ai-sdk-provider-claude-code 3.3.2 → 3.3.4

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
  }
@@ -1293,6 +1385,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1293
1385
  let wasTruncated = false;
1294
1386
  let costUsd;
1295
1387
  let durationMs;
1388
+ let modelUsage;
1296
1389
  const warnings = this.generateAllWarnings(options, messagesPrompt);
1297
1390
  if (messageWarnings) {
1298
1391
  messageWarnings.forEach((warning) => {
@@ -1327,7 +1420,8 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1327
1420
  messagesPrompt,
1328
1421
  outputStreamEnded,
1329
1422
  effectiveResume,
1330
- streamingContentParts
1423
+ streamingContentParts,
1424
+ this.settings.onStreamStart
1331
1425
  ) : messagesPrompt;
1332
1426
  this.logger.debug(
1333
1427
  `[claude-code] Executing query with streamingInput: ${wantsStreamInput}, session: ${effectiveResume ?? "new"}`
@@ -1346,6 +1440,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1346
1440
  this.setSessionId(message.session_id);
1347
1441
  costUsd = message.total_cost_usd;
1348
1442
  durationMs = message.duration_ms;
1443
+ modelUsage = message.modelUsage;
1349
1444
  if ("is_error" in message && message.is_error === true) {
1350
1445
  const errorMessage = "result" in message && typeof message.result === "string" ? message.result : "Claude Code CLI returned an error";
1351
1446
  throw Object.assign(new Error(errorMessage), { exitCode: 1 });
@@ -1421,6 +1516,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1421
1516
  ...this.sessionId !== void 0 && { sessionId: this.sessionId },
1422
1517
  ...costUsd !== void 0 && { costUsd },
1423
1518
  ...durationMs !== void 0 && { durationMs },
1519
+ ...modelUsage !== void 0 && { modelUsage },
1424
1520
  ...wasTruncated && { truncated: true }
1425
1521
  }
1426
1522
  }
@@ -1560,7 +1656,8 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1560
1656
  messagesPrompt,
1561
1657
  outputStreamEnded,
1562
1658
  effectiveResume,
1563
- streamingContentParts
1659
+ streamingContentParts,
1660
+ this.settings.onStreamStart
1564
1661
  ) : messagesPrompt;
1565
1662
  this.logger.debug(
1566
1663
  `[claude-code] Starting stream query with streamingInput: ${wantsStreamInput}, session: ${effectiveResume ?? "new"}`
@@ -2155,6 +2252,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
2155
2252
  costUsd: message.total_cost_usd
2156
2253
  },
2157
2254
  ...message.duration_ms !== void 0 && { durationMs: message.duration_ms },
2255
+ ...message.modelUsage !== void 0 && {
2256
+ modelUsage: message.modelUsage
2257
+ },
2158
2258
  // JSON validation warnings are collected during streaming and included
2159
2259
  // in providerMetadata since the AI SDK's finish event doesn't support
2160
2260
  // a top-level warnings field (unlike stream-start which was already emitted)