ai-sdk-provider-claude-code 2.0.4 → 2.1.0

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
@@ -45,12 +45,69 @@ npm install ai-sdk-provider-claude-code@ai-sdk-v4 ai@^4.3.16
45
45
 
46
46
  ## Zod Compatibility
47
47
 
48
- This package supports both **Zod 3** and **Zod 4**:
48
+ This package is **tested and compatible with both Zod 3 and Zod 4**, but there's an important peer dependency consideration:
49
49
 
50
- - **Zod 4** (Recommended): `npm install zod@^4.0.0`
51
- - ✅ **Zod 3** (Still supported): `npm install zod@^3.0.0`
50
+ ### Current Status
52
51
 
53
- The package uses Zod for schema validation and works seamlessly with both versions. All 302 tests pass with both Zod 3 and Zod 4. See `examples/zod4-compatibility-test.ts` for comprehensive compatibility tests.
52
+ - **Zod 3** (fully supported, no warnings)
53
+ - ⚠️ **Zod 4** (functional, but requires `--legacy-peer-deps`)
54
+
55
+ While this package declares support for both versions (`peerDependencies: "zod": "^3.0.0 || ^4.0.0"`), the underlying `@anthropic-ai/claude-agent-sdk` currently only declares support for Zod 3 (`peerDependencies: "zod": "^3.24.1"`).
56
+
57
+ **All 302 tests pass with both Zod 3 and Zod 4.** See `examples/zod4-compatibility-test.ts` for comprehensive compatibility verification.
58
+
59
+ ### Installation Instructions
60
+
61
+ **With Zod 3 (recommended for now):**
62
+
63
+ ```bash
64
+ npm install ai-sdk-provider-claude-code ai zod@^3.0.0
65
+ ```
66
+
67
+ **With Zod 4 (requires package manager-specific flags):**
68
+
69
+ For **npm**:
70
+
71
+ ```bash
72
+ npm install ai-sdk-provider-claude-code ai zod@^4.0.0 --legacy-peer-deps
73
+ ```
74
+
75
+ For **pnpm**:
76
+
77
+ ```bash
78
+ pnpm install ai-sdk-provider-claude-code ai zod@^4.0.0 --no-strict-peer-dependencies
79
+ # Or configure it project-wide:
80
+ pnpm config set strict-peer-dependencies false
81
+ ```
82
+
83
+ For **Yarn** (Berry/v2+):
84
+
85
+ ```bash
86
+ yarn add ai-sdk-provider-claude-code ai zod@^4.0.0
87
+ # Yarn's peer resolution typically doesn't error here
88
+ ```
89
+
90
+ ### For Package Developers
91
+
92
+ If you're developing with this package in your repository, add a configuration file to avoid needing the flag on every install:
93
+
94
+ **For npm** (`.npmrc`):
95
+
96
+ ```ini
97
+ # .npmrc
98
+ legacy-peer-deps=true
99
+ ```
100
+
101
+ **For pnpm** (`.npmrc`):
102
+
103
+ ```ini
104
+ # .npmrc
105
+ strict-peer-dependencies=false
106
+ ```
107
+
108
+ > **Note**: The `.npmrc` file in this repository is committed for CI/development consistency but is **not included in the published package** (it's excluded via the `files` field in `package.json`). End users will still need to use the appropriate flags when installing with Zod 4.
109
+
110
+ > **Temporary Workaround**: This configuration is needed until `@anthropic-ai/claude-agent-sdk` adds official Zod 4 support to their peer dependencies. Track progress in the [claude-agent-sdk repository](https://github.com/anthropics/anthropic-sdk-typescript).
54
111
 
55
112
  ## Installation
56
113
 
package/dist/index.cjs CHANGED
@@ -509,6 +509,12 @@ function mapClaudeCodeFinishReason(subtype) {
509
509
  var import_zod = require("zod");
510
510
  var import_fs = require("fs");
511
511
  var loggerFunctionSchema = import_zod.z.object({
512
+ debug: import_zod.z.any().refine((val) => typeof val === "function", {
513
+ message: "debug must be a function"
514
+ }),
515
+ info: import_zod.z.any().refine((val) => typeof val === "function", {
516
+ message: "info must be a function"
517
+ }),
512
518
  warn: import_zod.z.any().refine((val) => typeof val === "function", {
513
519
  message: "warn must be a function"
514
520
  }),
@@ -689,10 +695,16 @@ function validateSessionId(sessionId) {
689
695
 
690
696
  // src/logger.ts
691
697
  var defaultLogger = {
692
- warn: (message) => console.warn(message),
693
- error: (message) => console.error(message)
698
+ debug: (message) => console.debug(`[DEBUG] ${message}`),
699
+ info: (message) => console.info(`[INFO] ${message}`),
700
+ warn: (message) => console.warn(`[WARN] ${message}`),
701
+ error: (message) => console.error(`[ERROR] ${message}`)
694
702
  };
695
703
  var noopLogger = {
704
+ debug: () => {
705
+ },
706
+ info: () => {
707
+ },
696
708
  warn: () => {
697
709
  },
698
710
  error: () => {
@@ -707,83 +719,54 @@ function getLogger(logger) {
707
719
  }
708
720
  return logger;
709
721
  }
722
+ function createVerboseLogger(logger, verbose = false) {
723
+ if (verbose) {
724
+ return logger;
725
+ }
726
+ return {
727
+ debug: () => {
728
+ },
729
+ // No-op when not verbose
730
+ info: () => {
731
+ },
732
+ // No-op when not verbose
733
+ warn: logger.warn.bind(logger),
734
+ error: logger.error.bind(logger)
735
+ };
736
+ }
710
737
 
711
738
  // src/claude-code-language-model.ts
712
739
  var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
713
- var CLAUDE_CODE_TRUNCATION_WARNING = "Claude Code CLI output ended unexpectedly; returning truncated response from buffered text. Await upstream fix to avoid data loss.";
714
- var MIN_TRUNCATION_LENGTH = 1024;
715
- var POSITION_PATTERN = /position\s+(\d+)/i;
716
- function hasUnclosedJsonStructure(text) {
717
- let depth = 0;
718
- let inString = false;
719
- let escapeNext = false;
720
- for (let i = 0; i < text.length; i++) {
721
- const char = text[i];
722
- if (escapeNext) {
723
- escapeNext = false;
724
- continue;
725
- }
726
- if (inString) {
727
- if (char === "\\") {
728
- escapeNext = true;
729
- continue;
730
- }
731
- if (char === '"') {
732
- inString = false;
733
- }
734
- continue;
735
- }
736
- if (char === '"') {
737
- inString = true;
738
- continue;
739
- }
740
- if (char === "{" || char === "[") {
741
- depth++;
742
- continue;
743
- }
744
- if (char === "}" || char === "]") {
745
- if (depth > 0) {
746
- depth--;
747
- }
748
- }
749
- }
750
- return depth > 0 || inString;
751
- }
740
+ var CLAUDE_CODE_TRUNCATION_WARNING = "Claude Code SDK output ended unexpectedly; returning truncated response from buffered text. Await upstream fix to avoid data loss.";
741
+ var MIN_TRUNCATION_LENGTH = 512;
752
742
  function isClaudeCodeTruncationError(error, bufferedText) {
753
- if (!(error instanceof SyntaxError)) {
743
+ const isSyntaxError = error instanceof SyntaxError || // eslint-disable-next-line @typescript-eslint/no-explicit-any
744
+ typeof error?.name === "string" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
745
+ error.name.toLowerCase() === "syntaxerror";
746
+ if (!isSyntaxError) {
754
747
  return false;
755
748
  }
756
749
  if (!bufferedText) {
757
750
  return false;
758
751
  }
759
- const rawMessage = typeof error.message === "string" ? error.message : "";
752
+ const rawMessage = typeof error?.message === "string" ? error.message : "";
760
753
  const message = rawMessage.toLowerCase();
761
754
  const truncationIndicators = [
762
755
  "unexpected end of json input",
763
756
  "unexpected end of input",
764
757
  "unexpected end of string",
765
- "unterminated string"
758
+ "unexpected eof",
759
+ "end of file",
760
+ "unterminated string",
761
+ "unterminated string constant"
766
762
  ];
767
763
  if (!truncationIndicators.some((indicator) => message.includes(indicator))) {
768
764
  return false;
769
765
  }
770
- const positionMatch = rawMessage.match(POSITION_PATTERN);
771
- if (positionMatch) {
772
- const position = Number.parseInt(positionMatch[1], 10);
773
- if (Number.isFinite(position)) {
774
- const isNearBufferEnd = Math.abs(position - bufferedText.length) <= 16;
775
- if (isNearBufferEnd && position >= MIN_TRUNCATION_LENGTH) {
776
- return true;
777
- }
778
- if (!isNearBufferEnd) {
779
- return false;
780
- }
781
- }
782
- }
783
766
  if (bufferedText.length < MIN_TRUNCATION_LENGTH) {
784
767
  return false;
785
768
  }
786
- return hasUnclosedJsonStructure(bufferedText);
769
+ return true;
787
770
  }
788
771
  function isAbortError(err) {
789
772
  if (err && typeof err === "object") {
@@ -842,7 +825,8 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
842
825
  this.modelId = options.id;
843
826
  this.settings = options.settings ?? {};
844
827
  this.settingsValidationWarnings = options.settingsValidationWarnings ?? [];
845
- this.logger = getLogger(this.settings.logger);
828
+ const baseLogger = getLogger(this.settings.logger);
829
+ this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
846
830
  if (!this.modelId || typeof this.modelId !== "string" || this.modelId.trim() === "") {
847
831
  throw new import_provider2.NoSuchModelError({
848
832
  modelId: this.modelId,
@@ -930,7 +914,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
930
914
  );
931
915
  }
932
916
  if (length > _ClaudeCodeLanguageModel.MAX_TOOL_INPUT_WARN) {
933
- console.warn(
917
+ this.logger.warn(
934
918
  `[claude-code] Large tool input detected: ${length} bytes. Performance may be impacted. Consider chunking or reducing input size.`
935
919
  );
936
920
  }
@@ -1142,7 +1126,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1142
1126
  }
1143
1127
  }
1144
1128
  async doGenerate(options) {
1129
+ this.logger.debug(`[claude-code] Starting doGenerate request with model: ${this.modelId}`);
1145
1130
  const mode = options.responseFormat?.type === "json" ? { type: "object-json" } : { type: "regular" };
1131
+ this.logger.debug(
1132
+ `[claude-code] Request mode: ${mode.type}, response format: ${options.responseFormat?.type ?? "none"}`
1133
+ );
1146
1134
  const {
1147
1135
  messagesPrompt,
1148
1136
  warnings: messageWarnings,
@@ -1153,6 +1141,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1153
1141
  mode,
1154
1142
  options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
1155
1143
  );
1144
+ this.logger.debug(
1145
+ `[claude-code] Converted ${options.prompt.length} messages, hasImageParts: ${hasImageParts}`
1146
+ );
1156
1147
  const abortController = new AbortController();
1157
1148
  let abortListener;
1158
1149
  if (options.abortSignal?.aborted) {
@@ -1206,11 +1197,15 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1206
1197
  this.settings.resume ?? this.sessionId,
1207
1198
  streamingContentParts
1208
1199
  ) : messagesPrompt;
1200
+ this.logger.debug(
1201
+ `[claude-code] Executing query with streamingInput: ${wantsStreamInput}, session: ${this.settings.resume ?? this.sessionId ?? "new"}`
1202
+ );
1209
1203
  const response = (0, import_claude_agent_sdk.query)({
1210
1204
  prompt: sdkPrompt,
1211
1205
  options: queryOptions
1212
1206
  });
1213
1207
  for await (const message of response) {
1208
+ this.logger.debug(`[claude-code] Received message type: ${message.type}`);
1214
1209
  if (message.type === "assistant") {
1215
1210
  text += message.message.content.map((c) => c.type === "text" ? c.text : "").join("");
1216
1211
  } else if (message.type === "result") {
@@ -1218,6 +1213,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1218
1213
  this.setSessionId(message.session_id);
1219
1214
  costUsd = message.total_cost_usd;
1220
1215
  durationMs = message.duration_ms;
1216
+ this.logger.info(
1217
+ `[claude-code] Request completed - Session: ${message.session_id}, Cost: $${costUsd?.toFixed(4) ?? "N/A"}, Duration: ${durationMs ?? "N/A"}ms`
1218
+ );
1221
1219
  if ("usage" in message) {
1222
1220
  rawUsage = message.usage;
1223
1221
  usage = {
@@ -1225,18 +1223,30 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1225
1223
  outputTokens: message.usage.output_tokens ?? 0,
1226
1224
  totalTokens: (message.usage.cache_creation_input_tokens ?? 0) + (message.usage.cache_read_input_tokens ?? 0) + (message.usage.input_tokens ?? 0) + (message.usage.output_tokens ?? 0)
1227
1225
  };
1226
+ this.logger.debug(
1227
+ `[claude-code] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
1228
+ );
1228
1229
  }
1229
1230
  finishReason = mapClaudeCodeFinishReason(message.subtype);
1231
+ this.logger.debug(`[claude-code] Finish reason: ${finishReason}`);
1230
1232
  } else if (message.type === "system" && message.subtype === "init") {
1231
1233
  this.setSessionId(message.session_id);
1234
+ this.logger.info(`[claude-code] Session initialized: ${message.session_id}`);
1232
1235
  }
1233
1236
  }
1234
1237
  } catch (error) {
1235
1238
  done();
1239
+ this.logger.debug(
1240
+ `[claude-code] Error during doGenerate: ${error instanceof Error ? error.message : String(error)}`
1241
+ );
1236
1242
  if (isAbortError(error)) {
1243
+ this.logger.debug("[claude-code] Request aborted by user");
1237
1244
  throw options.abortSignal?.aborted ? options.abortSignal.reason : error;
1238
1245
  }
1239
1246
  if (isClaudeCodeTruncationError(error, text)) {
1247
+ this.logger.warn(
1248
+ `[claude-code] Detected truncated response, returning ${text.length} characters of buffered text`
1249
+ );
1240
1250
  wasTruncated = true;
1241
1251
  finishReason = "length";
1242
1252
  warnings.push({
@@ -1279,7 +1289,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1279
1289
  };
1280
1290
  }
1281
1291
  async doStream(options) {
1292
+ this.logger.debug(`[claude-code] Starting doStream request with model: ${this.modelId}`);
1282
1293
  const mode = options.responseFormat?.type === "json" ? { type: "object-json" } : { type: "regular" };
1294
+ this.logger.debug(
1295
+ `[claude-code] Stream mode: ${mode.type}, response format: ${options.responseFormat?.type ?? "none"}`
1296
+ );
1283
1297
  const {
1284
1298
  messagesPrompt,
1285
1299
  warnings: messageWarnings,
@@ -1290,6 +1304,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1290
1304
  mode,
1291
1305
  options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
1292
1306
  );
1307
+ this.logger.debug(
1308
+ `[claude-code] Converted ${options.prompt.length} messages for streaming, hasImageParts: ${hasImageParts}`
1309
+ );
1293
1310
  const abortController = new AbortController();
1294
1311
  let abortListener;
1295
1312
  if (options.abortSignal?.aborted) {
@@ -1383,16 +1400,19 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1383
1400
  this.settings.resume ?? this.sessionId,
1384
1401
  streamingContentParts
1385
1402
  ) : messagesPrompt;
1403
+ this.logger.debug(
1404
+ `[claude-code] Starting stream query with streamingInput: ${wantsStreamInput}, session: ${this.settings.resume ?? this.sessionId ?? "new"}`
1405
+ );
1386
1406
  const response = (0, import_claude_agent_sdk.query)({
1387
1407
  prompt: sdkPrompt,
1388
1408
  options: queryOptions
1389
1409
  });
1390
1410
  for await (const message of response) {
1411
+ this.logger.debug(`[claude-code] Stream received message type: ${message.type}`);
1391
1412
  if (message.type === "assistant") {
1392
1413
  if (!message.message?.content) {
1393
- console.warn(
1394
- `[claude-code] Unexpected assistant message structure: missing content field. Message type: ${message.type}. This may indicate an SDK protocol violation.`,
1395
- message
1414
+ this.logger.warn(
1415
+ `[claude-code] Unexpected assistant message structure: missing content field. Message type: ${message.type}. This may indicate an SDK protocol violation.`
1396
1416
  );
1397
1417
  continue;
1398
1418
  }
@@ -1408,9 +1428,15 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1408
1428
  callEmitted: false
1409
1429
  };
1410
1430
  toolStates.set(toolId, state);
1431
+ this.logger.debug(
1432
+ `[claude-code] New tool use detected - Tool: ${tool3.name}, ID: ${toolId}`
1433
+ );
1411
1434
  }
1412
1435
  state.name = tool3.name;
1413
1436
  if (!state.inputStarted) {
1437
+ this.logger.debug(
1438
+ `[claude-code] Tool input started - Tool: ${tool3.name}, ID: ${toolId}`
1439
+ );
1414
1440
  controller.enqueue({
1415
1441
  type: "tool-input-start",
1416
1442
  id: toolId,
@@ -1463,9 +1489,8 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1463
1489
  }
1464
1490
  } else if (message.type === "user") {
1465
1491
  if (!message.message?.content) {
1466
- console.warn(
1467
- `[claude-code] Unexpected user message structure: missing content field. Message type: ${message.type}. This may indicate an SDK protocol violation.`,
1468
- message
1492
+ this.logger.warn(
1493
+ `[claude-code] Unexpected user message structure: missing content field. Message type: ${message.type}. This may indicate an SDK protocol violation.`
1469
1494
  );
1470
1495
  continue;
1471
1496
  }
@@ -1473,8 +1498,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1473
1498
  for (const result of this.extractToolResults(content)) {
1474
1499
  let state = toolStates.get(result.id);
1475
1500
  const toolName = result.name ?? state?.name ?? _ClaudeCodeLanguageModel.UNKNOWN_TOOL_NAME;
1501
+ this.logger.debug(
1502
+ `[claude-code] Tool result received - Tool: ${toolName}, ID: ${result.id}`
1503
+ );
1476
1504
  if (!state) {
1477
- console.warn(
1505
+ this.logger.warn(
1478
1506
  `[claude-code] Received tool result for unknown tool ID: ${result.id}`
1479
1507
  );
1480
1508
  state = {
@@ -1535,8 +1563,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1535
1563
  for (const error of this.extractToolErrors(content)) {
1536
1564
  let state = toolStates.get(error.id);
1537
1565
  const toolName = error.name ?? state?.name ?? _ClaudeCodeLanguageModel.UNKNOWN_TOOL_NAME;
1566
+ this.logger.debug(
1567
+ `[claude-code] Tool error received - Tool: ${toolName}, ID: ${error.id}`
1568
+ );
1538
1569
  if (!state) {
1539
- console.warn(
1570
+ this.logger.warn(
1540
1571
  `[claude-code] Received tool error for unknown tool ID: ${error.id}`
1541
1572
  );
1542
1573
  state = {
@@ -1572,6 +1603,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1572
1603
  }
1573
1604
  } else if (message.type === "result") {
1574
1605
  done();
1606
+ this.logger.info(
1607
+ `[claude-code] Stream completed - Session: ${message.session_id}, Cost: $${message.total_cost_usd?.toFixed(4) ?? "N/A"}, Duration: ${message.duration_ms ?? "N/A"}ms`
1608
+ );
1575
1609
  let rawUsage;
1576
1610
  if ("usage" in message) {
1577
1611
  rawUsage = message.usage;
@@ -1580,10 +1614,14 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1580
1614
  outputTokens: message.usage.output_tokens ?? 0,
1581
1615
  totalTokens: (message.usage.cache_creation_input_tokens ?? 0) + (message.usage.cache_read_input_tokens ?? 0) + (message.usage.input_tokens ?? 0) + (message.usage.output_tokens ?? 0)
1582
1616
  };
1617
+ this.logger.debug(
1618
+ `[claude-code] Stream token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
1619
+ );
1583
1620
  }
1584
1621
  const finishReason = mapClaudeCodeFinishReason(
1585
1622
  message.subtype
1586
1623
  );
1624
+ this.logger.debug(`[claude-code] Stream finish reason: ${finishReason}`);
1587
1625
  this.setSessionId(message.session_id);
1588
1626
  if (options.responseFormat?.type === "json" && accumulatedText) {
1589
1627
  const extractedJson = this.handleJsonExtraction(accumulatedText, streamWarnings);
@@ -1632,6 +1670,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1632
1670
  });
1633
1671
  } else if (message.type === "system" && message.subtype === "init") {
1634
1672
  this.setSessionId(message.session_id);
1673
+ this.logger.info(`[claude-code] Stream session initialized: ${message.session_id}`);
1635
1674
  controller.enqueue({
1636
1675
  type: "response-metadata",
1637
1676
  id: message.session_id,
@@ -1641,10 +1680,17 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1641
1680
  }
1642
1681
  }
1643
1682
  finalizeToolCalls();
1683
+ this.logger.debug("[claude-code] Stream finalized, closing stream");
1644
1684
  controller.close();
1645
1685
  } catch (error) {
1646
1686
  done();
1687
+ this.logger.debug(
1688
+ `[claude-code] Error during doStream: ${error instanceof Error ? error.message : String(error)}`
1689
+ );
1647
1690
  if (isClaudeCodeTruncationError(error, accumulatedText)) {
1691
+ this.logger.warn(
1692
+ `[claude-code] Detected truncated stream response, returning ${accumulatedText.length} characters of buffered text`
1693
+ );
1648
1694
  const truncationWarning = {
1649
1695
  type: "other",
1650
1696
  message: CLAUDE_CODE_TRUNCATION_WARNING