| ... | @@ -31,7 +31,7 @@ import { | ... | @@ -31,7 +31,7 @@ import { |
| 31 | ProviderThreadId, | 31 | ProviderThreadId, |
| 32 | ProviderTurnId, | 32 | ProviderTurnId, |
| 33 | } from "@t3tools/contracts"; | 33 | } from "@t3tools/contracts"; |
| 34 | import { Cause, DateTime, Deferred, Effect, Layer, Queue, Random, Stream } from "effect"; | 34 | import { Cause, DateTime, Deferred, Effect, Layer, Queue, Random, Ref, Stream } from "effect"; |
| 35 | | 35 | |
| 36 | import { | 36 | import { |
| 37 | ProviderAdapterProcessError, | 37 | ProviderAdapterProcessError, |
| ... | @@ -312,25 +312,12 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -312,25 +312,12 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 312 | readonly options: ClaudeQueryOptions; | 312 | readonly options: ClaudeQueryOptions; |
| 313 | }) => query({ prompt: input.prompt, options: input.options }) as ClaudeQueryRuntime); | 313 | }) => query({ prompt: input.prompt, options: input.options }) as ClaudeQueryRuntime); |
| 314 | | 314 | |
| 315 | const sessions = new Map< | 315 | const sessions = new Map<ProviderSessionId, ClaudeSessionContext>(); |
| 316 | ReturnType<typeof ProviderSessionId.makeUnsafe>, | | |
| 317 | ClaudeSessionContext | | |
| 318 | >(); | | |
| 319 | const runtimeEventQueue = yield* Queue.unbounded<ProviderRuntimeEvent>(); | 316 | const runtimeEventQueue = yield* Queue.unbounded<ProviderRuntimeEvent>(); |
| 320 | | 317 | |
| 321 | const now = (): Effect.Effect<string> => | 318 | const nowIso = Effect.map(DateTime.now, DateTime.formatIso); |
| 322 | DateTime.now.pipe(Effect.map(DateTime.formatIso)); | 319 | const nextEventId = Effect.map(Random.nextUUIDv4, (id) => EventId.makeUnsafe(id)); |
| 323 | const randomId = (): Effect.Effect<string> => Random.nextUUIDv4; | 320 | const makeEventStamp = () => Effect.all({ eventId: nextEventId, createdAt: nowIso }); |
| 324 | const makeEventId = (): Effect.Effect<ReturnType<typeof EventId.makeUnsafe>> => | | |
| 325 | randomId().pipe(Effect.map((id) => EventId.makeUnsafe(id))); | | |
| 326 | const makeEventStamp = (): Effect.Effect<{ | | |
| 327 | readonly eventId: ReturnType<typeof EventId.makeUnsafe>; | | |
| 328 | readonly createdAt: string; | | |
| 329 | }> => | | |
| 330 | Effect.all({ | | |
| 331 | eventId: makeEventId(), | | |
| 332 | createdAt: now(), | | |
| 333 | }); | | |
| 334 | | 321 | |
| 335 | const offerRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> => | 322 | const offerRuntimeEvent = (event: ProviderRuntimeEvent): Effect.Effect<void> => |
| 336 | Queue.offer(runtimeEventQueue, event).pipe(Effect.asVoid); | 323 | Queue.offer(runtimeEventQueue, event).pipe(Effect.asVoid); |
| ... | @@ -347,7 +334,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -347,7 +334,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 347 | Effect.gen(function* () { | 334 | Effect.gen(function* () { |
| 348 | const threadId = | 335 | const threadId = |
| 349 | context.session.threadId ?? | 336 | context.session.threadId ?? |
| 350 | ProviderThreadId.makeUnsafe(`claude-thread-${yield* randomId()}`); | 337 | ProviderThreadId.makeUnsafe(`claude-thread-${yield* Random.nextUUIDv4}`); |
| 351 | return { | 338 | return { |
| 352 | threadId, | 339 | threadId, |
| 353 | turns: context.turns.map((turn) => ({ | 340 | turns: context.turns.map((turn) => ({ |
| ... | @@ -360,9 +347,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -360,9 +347,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 360 | const updateResumeCursor = (context: ClaudeSessionContext): Effect.Effect<void> => | 347 | const updateResumeCursor = (context: ClaudeSessionContext): Effect.Effect<void> => |
| 361 | Effect.gen(function* () { | 348 | Effect.gen(function* () { |
| 362 | const threadId = context.session.threadId; | 349 | const threadId = context.session.threadId; |
| 363 | if (!threadId) { | 350 | if (!threadId) return; |
| 364 | return; | | |
| 365 | } | | |
| 366 | | 351 | |
| 367 | const resumeCursor = { | 352 | const resumeCursor = { |
| 368 | threadId, | 353 | threadId, |
| ... | @@ -374,7 +359,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -374,7 +359,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 374 | context.session = { | 359 | context.session = { |
| 375 | ...context.session, | 360 | ...context.session, |
| 376 | resumeCursor, | 361 | resumeCursor, |
| 377 | updatedAt: yield* now(), | 362 | updatedAt: yield* nowIso, |
| 378 | }; | 363 | }; |
| 379 | }); | 364 | }); |
| 380 | | 365 | |
| ... | @@ -387,7 +372,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -387,7 +372,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 387 | const changed = context.session.threadId !== nextThreadId; | 372 | const changed = context.session.threadId !== nextThreadId; |
| 388 | | 373 | |
| 389 | if (changed) { | 374 | if (changed) { |
| 390 | const updatedAt = yield* now(); | 375 | const updatedAt = yield* nowIso; |
| 391 | context.session = { | 376 | context.session = { |
| 392 | ...context.session, | 377 | ...context.session, |
| 393 | threadId: nextThreadId, | 378 | threadId: nextThreadId, |
| ... | @@ -487,7 +472,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -487,7 +472,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 487 | ...(errorMessage ? { errorMessage } : {}), | 472 | ...(errorMessage ? { errorMessage } : {}), |
| 488 | }); | 473 | }); |
| 489 | | 474 | |
| 490 | const updatedAt = yield* now(); | 475 | const updatedAt = yield* nowIso; |
| 491 | context.turnState = undefined; | 476 | context.turnState = undefined; |
| 492 | context.session = { | 477 | context.session = { |
| 493 | ...context.session, | 478 | ...context.session, |
| ... | @@ -697,9 +682,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -697,9 +682,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 697 | options?: { readonly emitExitEvent?: boolean }, | 682 | options?: { readonly emitExitEvent?: boolean }, |
| 698 | ): Effect.Effect<void> => | 683 | ): Effect.Effect<void> => |
| 699 | Effect.gen(function* () { | 684 | Effect.gen(function* () { |
| 700 | if (context.stopped) { | 685 | if (context.stopped) return; |
| 701 | return; | | |
| 702 | } | | |
| 703 | | 686 | |
| 704 | context.stopped = true; | 687 | context.stopped = true; |
| 705 | | 688 | |
| ... | @@ -725,11 +708,11 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -725,11 +708,11 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 725 | yield* completeTurn(context, "interrupted", "Session stopped."); | 708 | yield* completeTurn(context, "interrupted", "Session stopped."); |
| 726 | } | 709 | } |
| 727 | | 710 | |
| 728 | yield* Queue.offer(context.promptQueue, { type: "terminate" }).pipe(Effect.ignore); | 711 | yield* Queue.shutdown(context.promptQueue); |
| 729 | | 712 | |
| 730 | yield* Effect.sync(() => context.query.close()).pipe(Effect.ignoreCause); | 713 | context.query.close(); |
| 731 | | 714 | |
| 732 | const updatedAt = yield* now(); | 715 | const updatedAt = yield* nowIso; |
| 733 | context.session = { | 716 | context.session = { |
| 734 | ...context.session, | 717 | ...context.session, |
| 735 | status: "closed", | 718 | status: "closed", |
| ... | @@ -786,44 +769,31 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -786,44 +769,31 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 786 | }); | 769 | }); |
| 787 | } | 770 | } |
| 788 | | 771 | |
| 789 | const startedAt = yield* now(); | 772 | const startedAt = yield* nowIso; |
| 790 | const sessionId = ProviderSessionId.makeUnsafe(`claude-session-${yield* randomId()}`); | 773 | const sessionId = ProviderSessionId.makeUnsafe( |
| | 774 | `claude-session-${yield* Random.nextUUIDv4}`, |
| | 775 | ); |
| 791 | const resumeState = readClaudeResumeState(input.resumeCursor); | 776 | const resumeState = readClaudeResumeState(input.resumeCursor); |
| 792 | const threadId = | 777 | const threadId = |
| 793 | resumeState?.threadId ?? | 778 | resumeState?.threadId ?? |
| 794 | ProviderThreadId.makeUnsafe(`claude-thread-${yield* randomId()}`); | 779 | ProviderThreadId.makeUnsafe(`claude-thread-${yield* Random.nextUUIDv4}`); |
| 795 | | 780 | |
| 796 | const promptQueue = yield* Queue.unbounded<PromptQueueItem>(); | 781 | const promptQueue = yield* Queue.unbounded<PromptQueueItem>(); |
| 797 | const prompt: AsyncIterable<SDKUserMessage> = { | 782 | const prompt = Stream.fromQueue(promptQueue).pipe( |
| 798 | [Symbol.asyncIterator]() { | 783 | Stream.filter((item) => item.type === "message"), |
| 799 | return { | 784 | Stream.map((item) => item.message), |
| 800 | next: async (): Promise<IteratorResult<SDKUserMessage>> => { | 785 | Stream.toAsyncIterable, |
| 801 | const item = await Effect.runPromise(Queue.take(promptQueue)); | 786 | ); |
| 802 | if (item.type === "terminate") { | | |
| 803 | return { | | |
| 804 | done: true, | | |
| 805 | value: undefined, | | |
| 806 | }; | | |
| 807 | } | | |
| 808 | | | |
| 809 | return { | | |
| 810 | done: false, | | |
| 811 | value: item.message, | | |
| 812 | }; | | |
| 813 | }, | | |
| 814 | }; | | |
| 815 | }, | | |
| 816 | }; | | |
| 817 | | 787 | |
| 818 | const pendingApprovals = new Map<ApprovalRequestId, PendingApproval>(); | 788 | const pendingApprovals = new Map<ApprovalRequestId, PendingApproval>(); |
| 819 | const inFlightTools = new Map<number, ToolInFlight>(); | 789 | const inFlightTools = new Map<number, ToolInFlight>(); |
| 820 | | 790 | |
| 821 | let contextRef: ClaudeSessionContext | undefined; | 791 | const contextRef = yield* Ref.make<ClaudeSessionContext | undefined>(undefined); |
| 822 | | 792 | |
| 823 | const canUseTool: CanUseTool = (toolName, toolInput, callbackOptions) => | 793 | const canUseTool: CanUseTool = (toolName, toolInput, callbackOptions) => |
| 824 | Effect.runPromise( | 794 | Effect.runPromise( |
| 825 | Effect.gen(function* () { | 795 | Effect.gen(function* () { |
| 826 | const context = contextRef; | 796 | const context = yield* Ref.get(contextRef); |
| 827 | if (!context) { | 797 | if (!context) { |
| 828 | return { | 798 | return { |
| 829 | behavior: "deny", | 799 | behavior: "deny", |
| ... | @@ -839,7 +809,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -839,7 +809,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 839 | } satisfies PermissionResult; | 809 | } satisfies PermissionResult; |
| 840 | } | 810 | } |
| 841 | | 811 | |
| 842 | const requestId = ApprovalRequestId.makeUnsafe(yield* randomId()); | 812 | const requestId = ApprovalRequestId.makeUnsafe(yield* Random.nextUUIDv4); |
| 843 | const requestKind = classifyRequestKind(toolName); | 813 | const requestKind = classifyRequestKind(toolName); |
| 844 | const detail = summarizeToolRequest(toolName, toolInput); | 814 | const detail = summarizeToolRequest(toolName, toolInput); |
| 845 | const decisionDeferred = yield* Deferred.make<ProviderApprovalDecision>(); | 815 | const decisionDeferred = yield* Deferred.make<ProviderApprovalDecision>(); |
| ... | @@ -988,7 +958,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -988,7 +958,7 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 988 | lastThreadStartedId: undefined, | 958 | lastThreadStartedId: undefined, |
| 989 | stopped: false, | 959 | stopped: false, |
| 990 | }; | 960 | }; |
| 991 | contextRef = context; | 961 | yield* Ref.set(contextRef, context); |
| 992 | sessions.set(sessionId, context); | 962 | sessions.set(sessionId, context); |
| 993 | | 963 | |
| 994 | const sessionStartedStamp = yield* makeEventStamp(); | 964 | const sessionStartedStamp = yield* makeEventStamp(); |
| ... | @@ -1027,16 +997,16 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { | ... | @@ -1027,16 +997,16 @@ function makeClaudeCodeAdapter(options?: ClaudeCodeAdapterLiveOptions) { |
| 1027 | }); | 997 | }); |
| 1028 | } | 998 | } |
| 1029 | | 999 | |
| 1030 | const turnId = ProviderTurnId.makeUnsafe(`claude-turn-${yield* randomId()}`); | 1000 | const turnId = ProviderTurnId.makeUnsafe(`claude-turn-${yield* Random.nextUUIDv4}`); |
| 1031 | const turnState: ClaudeTurnState = { | 1001 | const turnState: ClaudeTurnState = { |
| 1032 | turnId, | 1002 | turnId, |
| 1033 | assistantItemId: `claude-message-${yield* randomId()}`, | 1003 | assistantItemId: `claude-message-${yield* Random.nextUUIDv4}`, |
| 1034 | startedAt: yield* now(), | 1004 | startedAt: yield* nowIso, |
| 1035 | items: [], | 1005 | items: [], |
| 1036 | messageCompleted: false, | 1006 | messageCompleted: false, |
| 1037 | }; | 1007 | }; |
| 1038 | | 1008 | |
| 1039 | const updatedAt = yield* now(); | 1009 | const updatedAt = yield* nowIso; |
| 1040 | context.turnState = turnState; | 1010 | context.turnState = turnState; |
| 1041 | context.session = { | 1011 | context.session = { |
| 1042 | ...context.session, | 1012 | ...context.session, |