ai-sdk-provider-claude-code 3.3.3 → 3.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -310,6 +310,68 @@ const model = claudeCode('sonnet', {
310
310
  });
311
311
  ```
312
312
 
313
+ ## Mid-Session Message Injection
314
+
315
+ This provider supports **mid-session message injection** for supervisor patterns, allowing you to interrupt, redirect, or provide feedback to an agent during execution.
316
+
317
+ ```typescript
318
+ import { streamText } from 'ai';
319
+ import { claudeCode, type MessageInjector } from 'ai-sdk-provider-claude-code';
320
+
321
+ let injector: MessageInjector | null = null;
322
+
323
+ const result = streamText({
324
+ model: claudeCode('haiku', {
325
+ streamingInput: 'always', // Required for injection
326
+ onStreamStart: (inj) => {
327
+ injector = inj;
328
+
329
+ // Example: Inject after 5 seconds
330
+ setTimeout(() => {
331
+ injector?.inject('STOP! Change of plans - do something else.');
332
+ }, 5000);
333
+ },
334
+ }),
335
+ prompt: 'Write 10 files with poems...',
336
+ });
337
+
338
+ for await (const chunk of result.textStream) {
339
+ process.stdout.write(chunk);
340
+ }
341
+ ```
342
+
343
+ **Requirements:**
344
+
345
+ - `streamingInput: 'always'` or `'auto'` with `canUseTool` set
346
+ - Messages injected via `inject(content)` are delivered to the agent mid-turn
347
+
348
+ **Important:** Injection works between tool calls, not during continuous text generation. Use tasks that involve tool usage (file operations, bash commands, etc.) for effective mid-turn interruption.
349
+
350
+ **Use Cases:**
351
+
352
+ - Stop an agent mid-task
353
+ - Redirect to a different goal
354
+ - Provide real-time feedback
355
+ - Implement human-in-the-loop approval workflows
356
+
357
+ **API:**
358
+
359
+ - `inject(content: string, onResult?: (delivered: boolean) => void)` - Inject a user message. Optional callback reports delivery status.
360
+ - `close()` - Signal no more messages will be injected
361
+
362
+ **Delivery Tracking:**
363
+
364
+ ```typescript
365
+ injector.inject('STOP!', (delivered) => {
366
+ if (!delivered) {
367
+ // Session ended before message was delivered
368
+ // Handle retry via session resume, etc.
369
+ }
370
+ });
371
+ ```
372
+
373
+ See [examples/message-injection.ts](examples/message-injection.ts) for complete examples including conditional injection and supervisor approval patterns.
374
+
313
375
  ## Image Inputs (Streaming Only)
314
376
 
315
377
  - Enable streaming input (`streamingInput: 'always'` or provide `canUseTool`) before sending images.
package/dist/index.cjs CHANGED
@@ -572,6 +572,9 @@ var claudeCodeSettingsSchema = import_zod.z.object({
572
572
  // Callback invoked when Query object is created - for mid-stream injection via streamInput()
573
573
  onQueryCreated: import_zod.z.any().refine((val) => val === void 0 || typeof val === "function", {
574
574
  message: "onQueryCreated must be a function"
575
+ }).optional(),
576
+ onStreamStart: import_zod.z.any().refine((val) => val === void 0 || typeof val === "function", {
577
+ message: "onStreamStart must be a function"
575
578
  }).optional()
576
579
  }).strict();
577
580
  function validateModelId(modelId) {
@@ -806,9 +809,66 @@ function convertClaudeCodeUsage(usage) {
806
809
  raw: usage
807
810
  };
808
811
  }
809
- function toAsyncIterablePrompt(messagesPrompt, outputStreamEnded, sessionId, contentParts) {
812
+ function createMessageInjector() {
813
+ const queue = [];
814
+ let closed = false;
815
+ let resolver = null;
816
+ const injector = {
817
+ inject(content, onResult) {
818
+ if (closed) {
819
+ onResult?.(false);
820
+ return;
821
+ }
822
+ const item = { content, onResult };
823
+ if (resolver) {
824
+ const r = resolver;
825
+ resolver = null;
826
+ r(item);
827
+ } else {
828
+ queue.push(item);
829
+ }
830
+ },
831
+ close() {
832
+ closed = true;
833
+ if (resolver && queue.length === 0) {
834
+ resolver(null);
835
+ resolver = null;
836
+ }
837
+ }
838
+ };
839
+ const getNextItem = () => {
840
+ if (queue.length > 0) {
841
+ const item = queue.shift();
842
+ if (!item) {
843
+ return Promise.resolve(null);
844
+ }
845
+ return Promise.resolve(item);
846
+ }
847
+ if (closed) {
848
+ return Promise.resolve(null);
849
+ }
850
+ return new Promise((resolve) => {
851
+ resolver = (item) => {
852
+ resolve(item);
853
+ };
854
+ });
855
+ };
856
+ const notifySessionEnded = () => {
857
+ for (const item of queue) {
858
+ item.onResult?.(false);
859
+ }
860
+ queue.length = 0;
861
+ closed = true;
862
+ if (resolver) {
863
+ resolver(null);
864
+ resolver = null;
865
+ }
866
+ };
867
+ return { injector, getNextItem, notifySessionEnded };
868
+ }
869
+ function toAsyncIterablePrompt(messagesPrompt, outputStreamEnded, sessionId, contentParts, onStreamStart) {
810
870
  const content = contentParts && contentParts.length > 0 ? contentParts : [{ type: "text", text: messagesPrompt }];
811
- const msg = {
871
+ const initialMsg = {
812
872
  type: "user",
813
873
  message: {
814
874
  role: "user",
@@ -817,10 +877,42 @@ function toAsyncIterablePrompt(messagesPrompt, outputStreamEnded, sessionId, con
817
877
  parent_tool_use_id: null,
818
878
  session_id: sessionId ?? ""
819
879
  };
880
+ if (!onStreamStart) {
881
+ return {
882
+ async *[Symbol.asyncIterator]() {
883
+ yield initialMsg;
884
+ await outputStreamEnded;
885
+ }
886
+ };
887
+ }
888
+ const { injector, getNextItem, notifySessionEnded } = createMessageInjector();
820
889
  return {
821
890
  async *[Symbol.asyncIterator]() {
822
- yield msg;
823
- await outputStreamEnded;
891
+ yield initialMsg;
892
+ onStreamStart(injector);
893
+ let streamEnded = false;
894
+ void outputStreamEnded.then(() => {
895
+ streamEnded = true;
896
+ notifySessionEnded();
897
+ });
898
+ while (!streamEnded) {
899
+ const item = await Promise.race([getNextItem(), outputStreamEnded.then(() => null)]);
900
+ if (item === null) {
901
+ await outputStreamEnded;
902
+ break;
903
+ }
904
+ const sdkMsg = {
905
+ type: "user",
906
+ message: {
907
+ role: "user",
908
+ content: [{ type: "text", text: item.content }]
909
+ },
910
+ parent_tool_use_id: null,
911
+ session_id: sessionId ?? ""
912
+ };
913
+ yield sdkMsg;
914
+ item.onResult?.(true);
915
+ }
824
916
  }
825
917
  };
826
918
  }
@@ -1328,7 +1420,8 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1328
1420
  messagesPrompt,
1329
1421
  outputStreamEnded,
1330
1422
  effectiveResume,
1331
- streamingContentParts
1423
+ streamingContentParts,
1424
+ this.settings.onStreamStart
1332
1425
  ) : messagesPrompt;
1333
1426
  this.logger.debug(
1334
1427
  `[claude-code] Executing query with streamingInput: ${wantsStreamInput}, session: ${effectiveResume ?? "new"}`
@@ -1563,7 +1656,8 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1563
1656
  messagesPrompt,
1564
1657
  outputStreamEnded,
1565
1658
  effectiveResume,
1566
- streamingContentParts
1659
+ streamingContentParts,
1660
+ this.settings.onStreamStart
1567
1661
  ) : messagesPrompt;
1568
1662
  this.logger.debug(
1569
1663
  `[claude-code] Starting stream query with streamingInput: ${wantsStreamInput}, session: ${effectiveResume ?? "new"}`
@@ -1938,6 +2032,23 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1938
2032
  );
1939
2033
  continue;
1940
2034
  }
2035
+ if (textPartId) {
2036
+ const closedTextId = textPartId;
2037
+ controller.enqueue({
2038
+ type: "text-end",
2039
+ id: closedTextId
2040
+ });
2041
+ textPartId = void 0;
2042
+ for (const [blockIndex, blockTextId] of textBlocksByIndex) {
2043
+ if (blockTextId === closedTextId) {
2044
+ textBlocksByIndex.delete(blockIndex);
2045
+ break;
2046
+ }
2047
+ }
2048
+ accumulatedText = "";
2049
+ streamedTextLength = 0;
2050
+ this.logger.debug("[claude-code] Closed text part due to user message");
2051
+ }
1941
2052
  const sdkParentToolUseIdForResults = message.parent_tool_use_id;
1942
2053
  const content = message.message.content;
1943
2054
  for (const result of this.extractToolResults(content)) {