ai-sdk-provider-claude-code 3.0.0 → 3.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
@@ -269,6 +269,28 @@ console.log(result.object); // Guaranteed to match schema
269
269
  - 🔧 Tool management (MCP servers, permissions)
270
270
  - 🧩 Callbacks (hooks, canUseTool)
271
271
 
272
+ ## Agent SDK Options (Advanced)
273
+
274
+ This provider now exposes additional Agent SDK options directly (e.g. `betas`, `sandbox`,
275
+ `plugins`, `resumeSessionAt`, `enableFileCheckpointing`, `maxBudgetUsd`, `tools`,
276
+ `allowDangerouslySkipPermissions`).
277
+
278
+ For newer SDK options, use the `sdkOptions` escape hatch. It **overrides** explicit settings,
279
+ but provider-managed fields are ignored (`model`, `abortController`, `prompt`, `outputFormat`).
280
+ If you set `sdkOptions.resume`, it also drives the streaming prompt `session_id` so the SDK
281
+ and prompt target the same session.
282
+
283
+ ```ts
284
+ const model = claudeCode('sonnet', {
285
+ betas: ['context-1m-2025-08-07'],
286
+ sandbox: { enabled: true },
287
+ sdkOptions: {
288
+ maxBudgetUsd: 1,
289
+ resume: 'session-abc',
290
+ },
291
+ });
292
+ ```
293
+
272
294
  ## Image Inputs (Streaming Only)
273
295
 
274
296
  - Enable streaming input (`streamingInput: 'always'` or provide `canUseTool`) before sending images.
@@ -278,6 +300,37 @@ console.log(result.object); // Guaranteed to match schema
278
300
  - Use realistic image payloads—very small placeholders may result in the model asking for a different image.
279
301
  - `examples/images.ts` accepts a local image path and converts it to a data URL on the fly: `npx tsx examples/images.ts /absolute/path/to/image.png`.
280
302
 
303
+ ## Skills Support
304
+
305
+ Claude Code supports **Skills** - custom tools and capabilities defined in your user or project settings. To enable skills, configure both `settingSources` and `allowedTools`:
306
+
307
+ ```typescript
308
+ import { claudeCode } from 'ai-sdk-provider-claude-code';
309
+ import { streamText } from 'ai';
310
+
311
+ const result = await streamText({
312
+ model: claudeCode('sonnet', {
313
+ settingSources: ['user', 'project'],
314
+ allowedTools: ['Skill', 'Read', 'Write', 'Bash'],
315
+ }),
316
+ prompt: 'Use my /custom-skill to help with this task',
317
+ });
318
+ ```
319
+
320
+ **Requirements:**
321
+
322
+ - `settingSources` - Where to load skills from (`'user'`, `'project'`, `'local'`)
323
+ - `allowedTools` must include `'Skill'` to invoke skills
324
+
325
+ **Where to define Skills:**
326
+
327
+ - User: `~/.claude/skills/your-skill/SKILL.md`
328
+ - Project: `.claude/skills/your-skill/SKILL.md`
329
+
330
+ **Validation:** If you add `'Skill'` to `allowedTools` but forget to set `settingSources`, a validation warning will alert you that skills won't load.
331
+
332
+ See [examples/skills-management.ts](examples/skills-management.ts) for more examples.
333
+
281
334
  ## Limitations
282
335
 
283
336
  - Requires Node.js ≥ 18
package/dist/index.cjs CHANGED
@@ -467,6 +467,27 @@ var claudeCodeSettingsSchema = import_zod.z.object({
467
467
  resume: import_zod.z.string().optional(),
468
468
  allowedTools: import_zod.z.array(import_zod.z.string()).optional(),
469
469
  disallowedTools: import_zod.z.array(import_zod.z.string()).optional(),
470
+ betas: import_zod.z.array(import_zod.z.string()).optional(),
471
+ allowDangerouslySkipPermissions: import_zod.z.boolean().optional(),
472
+ enableFileCheckpointing: import_zod.z.boolean().optional(),
473
+ maxBudgetUsd: import_zod.z.number().min(0).optional(),
474
+ plugins: import_zod.z.array(
475
+ import_zod.z.object({
476
+ type: import_zod.z.string(),
477
+ path: import_zod.z.string()
478
+ }).passthrough()
479
+ ).optional(),
480
+ resumeSessionAt: import_zod.z.string().optional(),
481
+ sandbox: import_zod.z.any().refine((val) => val === void 0 || typeof val === "object", {
482
+ message: "sandbox must be an object"
483
+ }).optional(),
484
+ tools: import_zod.z.union([
485
+ import_zod.z.array(import_zod.z.string()),
486
+ import_zod.z.object({
487
+ type: import_zod.z.literal("preset"),
488
+ preset: import_zod.z.literal("claude_code")
489
+ })
490
+ ]).optional(),
470
491
  settingSources: import_zod.z.array(import_zod.z.enum(["user", "project", "local"])).optional(),
471
492
  streamingInput: import_zod.z.enum(["auto", "always", "off"]).optional(),
472
493
  // Hooks and tool-permission callback (permissive validation of shapes)
@@ -532,7 +553,8 @@ var claudeCodeSettingsSchema = import_zod.z.object({
532
553
  message: "stderr must be a function"
533
554
  }).optional(),
534
555
  strictMcpConfig: import_zod.z.boolean().optional(),
535
- extraArgs: import_zod.z.record(import_zod.z.string(), import_zod.z.union([import_zod.z.string(), import_zod.z.null()])).optional()
556
+ extraArgs: import_zod.z.record(import_zod.z.string(), import_zod.z.union([import_zod.z.string(), import_zod.z.null()])).optional(),
557
+ sdkOptions: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).optional()
536
558
  }).strict();
537
559
  function validateModelId(modelId) {
538
560
  const knownModels = ["opus", "sonnet", "haiku"];
@@ -587,6 +609,11 @@ function validateSettings(settings) {
587
609
  if (validSettings.disallowedTools) {
588
610
  validateToolNames(validSettings.disallowedTools, "disallowed");
589
611
  }
612
+ if (validSettings.allowedTools?.includes("Skill") && !validSettings.settingSources) {
613
+ warnings.push(
614
+ "allowedTools includes 'Skill' but settingSources is not set. Skills require settingSources (e.g., ['user', 'project']) to load skill definitions."
615
+ );
616
+ }
590
617
  return { valid: true, warnings, errors };
591
618
  } catch (error) {
592
619
  errors.push(`Validation error: ${error instanceof Error ? error.message : String(error)}`);
@@ -609,7 +636,9 @@ function validateSessionId(sessionId) {
609
636
 
610
637
  // src/logger.ts
611
638
  var defaultLogger = {
639
+ // eslint-disable-next-line no-console
612
640
  debug: (message) => console.debug(`[DEBUG] ${message}`),
641
+ // eslint-disable-next-line no-console
613
642
  info: (message) => console.info(`[INFO] ${message}`),
614
643
  warn: (message) => console.warn(`[WARN] ${message}`),
615
644
  error: (message) => console.error(`[ERROR] ${message}`)
@@ -691,6 +720,7 @@ function isAbortError(err) {
691
720
  return false;
692
721
  }
693
722
  var STREAMING_FEATURE_WARNING = "Claude Agent SDK features (hooks/MCP/images) require streaming input. Set `streamingInput: 'always'` or provide `canUseTool` (auto streams only when canUseTool is set).";
723
+ var SDK_OPTIONS_BLOCKLIST = /* @__PURE__ */ new Set(["model", "abortController", "prompt", "outputFormat"]);
694
724
  function createEmptyUsage() {
695
725
  return {
696
726
  inputTokens: {
@@ -795,6 +825,25 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
795
825
  const mapped = modelMap[this.modelId];
796
826
  return mapped ?? this.modelId;
797
827
  }
828
+ getSanitizedSdkOptions() {
829
+ if (!this.settings.sdkOptions || typeof this.settings.sdkOptions !== "object") {
830
+ return void 0;
831
+ }
832
+ const sanitized = { ...this.settings.sdkOptions };
833
+ const blockedKeys = Array.from(SDK_OPTIONS_BLOCKLIST).filter((key) => key in sanitized);
834
+ if (blockedKeys.length > 0) {
835
+ this.logger.warn(
836
+ `[claude-code] sdkOptions includes provider-managed fields (${blockedKeys.join(
837
+ ", "
838
+ )}); these will be ignored.`
839
+ );
840
+ blockedKeys.forEach((key) => delete sanitized[key]);
841
+ }
842
+ return sanitized;
843
+ }
844
+ getEffectiveResume(sdkOptions) {
845
+ return sdkOptions?.resume ?? this.settings.resume ?? this.sessionId;
846
+ }
798
847
  extractToolUses(content) {
799
848
  if (!Array.isArray(content)) {
800
849
  return [];
@@ -928,11 +977,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
928
977
  }
929
978
  return warnings;
930
979
  }
931
- createQueryOptions(abortController, responseFormat) {
980
+ createQueryOptions(abortController, responseFormat, stderrCollector, sdkOptions, effectiveResume) {
932
981
  const opts = {
933
982
  model: this.getModel(),
934
983
  abortController,
935
- resume: this.settings.resume ?? this.sessionId,
984
+ resume: effectiveResume ?? this.settings.resume ?? this.sessionId,
936
985
  pathToClaudeCodeExecutable: this.settings.pathToClaudeCodeExecutable,
937
986
  maxTurns: this.settings.maxTurns,
938
987
  maxThinkingTokens: this.settings.maxThinkingTokens,
@@ -944,6 +993,14 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
944
993
  continue: this.settings.continue,
945
994
  allowedTools: this.settings.allowedTools,
946
995
  disallowedTools: this.settings.disallowedTools,
996
+ betas: this.settings.betas,
997
+ allowDangerouslySkipPermissions: this.settings.allowDangerouslySkipPermissions,
998
+ enableFileCheckpointing: this.settings.enableFileCheckpointing,
999
+ maxBudgetUsd: this.settings.maxBudgetUsd,
1000
+ plugins: this.settings.plugins,
1001
+ resumeSessionAt: this.settings.resumeSessionAt,
1002
+ sandbox: this.settings.sandbox,
1003
+ tools: this.settings.tools,
947
1004
  mcpServers: this.settings.mcpServers,
948
1005
  canUseTool: this.settings.canUseTool
949
1006
  };
@@ -982,9 +1039,6 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
982
1039
  if (this.settings.forkSession !== void 0) {
983
1040
  opts.forkSession = this.settings.forkSession;
984
1041
  }
985
- if (this.settings.stderr !== void 0) {
986
- opts.stderr = this.settings.stderr;
987
- }
988
1042
  if (this.settings.strictMcpConfig !== void 0) {
989
1043
  opts.strictMcpConfig = this.settings.strictMcpConfig;
990
1044
  }
@@ -994,8 +1048,24 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
994
1048
  if (this.settings.hooks) {
995
1049
  opts.hooks = this.settings.hooks;
996
1050
  }
997
- if (this.settings.env !== void 0) {
998
- opts.env = { ...process.env, ...this.settings.env };
1051
+ const sdkOverrides = sdkOptions ? sdkOptions : void 0;
1052
+ const sdkEnv = sdkOverrides && typeof sdkOverrides.env === "object" && sdkOverrides.env !== null ? sdkOverrides.env : void 0;
1053
+ const sdkStderr = sdkOverrides && typeof sdkOverrides.stderr === "function" ? sdkOverrides.stderr : void 0;
1054
+ if (sdkOverrides) {
1055
+ const rest = { ...sdkOverrides };
1056
+ delete rest.env;
1057
+ delete rest.stderr;
1058
+ Object.assign(opts, rest);
1059
+ }
1060
+ const userStderrCallback = sdkStderr ?? this.settings.stderr;
1061
+ if (stderrCollector || userStderrCallback) {
1062
+ opts.stderr = (data) => {
1063
+ if (stderrCollector) stderrCollector(data);
1064
+ if (userStderrCallback) userStderrCallback(data);
1065
+ };
1066
+ }
1067
+ if (this.settings.env !== void 0 || sdkEnv !== void 0) {
1068
+ opts.env = { ...process.env, ...this.settings.env, ...sdkEnv };
999
1069
  }
1000
1070
  if (responseFormat?.type === "json" && responseFormat.schema) {
1001
1071
  opts.outputFormat = {
@@ -1005,7 +1075,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1005
1075
  }
1006
1076
  return opts;
1007
1077
  }
1008
- handleClaudeCodeError(error, messagesPrompt) {
1078
+ handleClaudeCodeError(error, messagesPrompt, collectedStderr) {
1009
1079
  if (isAbortError(error)) {
1010
1080
  throw error;
1011
1081
  }
@@ -1021,7 +1091,10 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1021
1091
  "unauthorized",
1022
1092
  "auth failed",
1023
1093
  "please login",
1024
- "claude login"
1094
+ "claude login",
1095
+ "/login",
1096
+ // CLI returns "Please run /login"
1097
+ "invalid api key"
1025
1098
  ];
1026
1099
  const errorMessage = isErrorWithMessage(error) && error.message ? error.message.toLowerCase() : "";
1027
1100
  const exitCode = isErrorWithCode(error) && typeof error.exitCode === "number" ? error.exitCode : void 0;
@@ -1041,11 +1114,13 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1041
1114
  });
1042
1115
  }
1043
1116
  const isRetryable = errorCode === "ENOENT" || errorCode === "ECONNREFUSED" || errorCode === "ETIMEDOUT" || errorCode === "ECONNRESET";
1117
+ const stderrFromError = isErrorWithCode(error) && typeof error.stderr === "string" ? error.stderr : void 0;
1118
+ const stderr = stderrFromError || collectedStderr || void 0;
1044
1119
  return createAPICallError({
1045
1120
  message: isErrorWithMessage(error) && error.message ? error.message : "Claude Code SDK error",
1046
1121
  code: errorCode || void 0,
1047
1122
  exitCode,
1048
- stderr: isErrorWithCode(error) && typeof error.stderr === "string" ? error.stderr : void 0,
1123
+ stderr,
1049
1124
  promptExcerpt: messagesPrompt.substring(0, 200),
1050
1125
  isRetryable
1051
1126
  });
@@ -1077,7 +1152,19 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1077
1152
  abortListener = () => abortController.abort(options.abortSignal?.reason);
1078
1153
  options.abortSignal.addEventListener("abort", abortListener, { once: true });
1079
1154
  }
1080
- const queryOptions = this.createQueryOptions(abortController, options.responseFormat);
1155
+ let collectedStderr = "";
1156
+ const stderrCollector = (data) => {
1157
+ collectedStderr += data;
1158
+ };
1159
+ const sdkOptions = this.getSanitizedSdkOptions();
1160
+ const effectiveResume = this.getEffectiveResume(sdkOptions);
1161
+ const queryOptions = this.createQueryOptions(
1162
+ abortController,
1163
+ options.responseFormat,
1164
+ stderrCollector,
1165
+ sdkOptions,
1166
+ effectiveResume
1167
+ );
1081
1168
  let text = "";
1082
1169
  let structuredOutput;
1083
1170
  let usage = createEmptyUsage();
@@ -1095,7 +1182,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1095
1182
  });
1096
1183
  }
1097
1184
  const modeSetting = this.settings.streamingInput ?? "auto";
1098
- const wantsStreamInput = modeSetting === "always" || modeSetting === "auto" && !!this.settings.canUseTool;
1185
+ const effectiveCanUseTool = sdkOptions?.canUseTool ?? this.settings.canUseTool;
1186
+ const effectivePermissionPromptToolName = sdkOptions?.permissionPromptToolName ?? this.settings.permissionPromptToolName;
1187
+ const wantsStreamInput = modeSetting === "always" || modeSetting === "auto" && !!effectiveCanUseTool;
1099
1188
  if (!wantsStreamInput && hasImageParts) {
1100
1189
  warnings.push({
1101
1190
  type: "other",
@@ -1108,7 +1197,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1108
1197
  done = () => resolve(void 0);
1109
1198
  });
1110
1199
  try {
1111
- if (this.settings.canUseTool && this.settings.permissionPromptToolName) {
1200
+ if (effectiveCanUseTool && effectivePermissionPromptToolName) {
1112
1201
  throw new Error(
1113
1202
  "canUseTool requires streamingInput mode ('auto' or 'always') and cannot be used with permissionPromptToolName (SDK constraint). Set streamingInput: 'auto' (or 'always') and remove permissionPromptToolName, or remove canUseTool."
1114
1203
  );
@@ -1116,11 +1205,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1116
1205
  const sdkPrompt = wantsStreamInput ? toAsyncIterablePrompt(
1117
1206
  messagesPrompt,
1118
1207
  outputStreamEnded,
1119
- this.settings.resume ?? this.sessionId,
1208
+ effectiveResume,
1120
1209
  streamingContentParts
1121
1210
  ) : messagesPrompt;
1122
1211
  this.logger.debug(
1123
- `[claude-code] Executing query with streamingInput: ${wantsStreamInput}, session: ${this.settings.resume ?? this.sessionId ?? "new"}`
1212
+ `[claude-code] Executing query with streamingInput: ${wantsStreamInput}, session: ${effectiveResume ?? "new"}`
1124
1213
  );
1125
1214
  const response = (0, import_claude_agent_sdk.query)({
1126
1215
  prompt: sdkPrompt,
@@ -1135,6 +1224,10 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1135
1224
  this.setSessionId(message.session_id);
1136
1225
  costUsd = message.total_cost_usd;
1137
1226
  durationMs = message.duration_ms;
1227
+ if ("is_error" in message && message.is_error === true) {
1228
+ const errorMessage = "result" in message && typeof message.result === "string" ? message.result : "Claude Code CLI returned an error";
1229
+ throw Object.assign(new Error(errorMessage), { exitCode: 1 });
1230
+ }
1138
1231
  if (message.subtype === "error_max_structured_output_retries") {
1139
1232
  throw new Error(
1140
1233
  "Failed to generate valid structured output after maximum retries. The model could not produce a response matching the required schema."
@@ -1180,7 +1273,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1180
1273
  message: CLAUDE_CODE_TRUNCATION_WARNING
1181
1274
  });
1182
1275
  } else {
1183
- throw this.handleClaudeCodeError(error, messagesPrompt);
1276
+ throw this.handleClaudeCodeError(error, messagesPrompt, collectedStderr);
1184
1277
  }
1185
1278
  } finally {
1186
1279
  if (options.abortSignal && abortListener) {
@@ -1231,7 +1324,19 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1231
1324
  abortListener = () => abortController.abort(options.abortSignal?.reason);
1232
1325
  options.abortSignal.addEventListener("abort", abortListener, { once: true });
1233
1326
  }
1234
- const queryOptions = this.createQueryOptions(abortController, options.responseFormat);
1327
+ let collectedStderr = "";
1328
+ const stderrCollector = (data) => {
1329
+ collectedStderr += data;
1330
+ };
1331
+ const sdkOptions = this.getSanitizedSdkOptions();
1332
+ const effectiveResume = this.getEffectiveResume(sdkOptions);
1333
+ const queryOptions = this.createQueryOptions(
1334
+ abortController,
1335
+ options.responseFormat,
1336
+ stderrCollector,
1337
+ sdkOptions,
1338
+ effectiveResume
1339
+ );
1235
1340
  if (queryOptions.includePartialMessages === void 0) {
1236
1341
  queryOptions.includePartialMessages = true;
1237
1342
  }
@@ -1245,7 +1350,9 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1245
1350
  });
1246
1351
  }
1247
1352
  const modeSetting = this.settings.streamingInput ?? "auto";
1248
- const wantsStreamInput = modeSetting === "always" || modeSetting === "auto" && !!this.settings.canUseTool;
1353
+ const effectiveCanUseTool = sdkOptions?.canUseTool ?? this.settings.canUseTool;
1354
+ const effectivePermissionPromptToolName = sdkOptions?.permissionPromptToolName ?? this.settings.permissionPromptToolName;
1355
+ const wantsStreamInput = modeSetting === "always" || modeSetting === "auto" && !!effectiveCanUseTool;
1249
1356
  if (!wantsStreamInput && hasImageParts) {
1250
1357
  warnings.push({
1251
1358
  type: "other",
@@ -1307,7 +1414,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1307
1414
  let hasReceivedStreamEvents = false;
1308
1415
  try {
1309
1416
  controller.enqueue({ type: "stream-start", warnings });
1310
- if (this.settings.canUseTool && this.settings.permissionPromptToolName) {
1417
+ if (effectiveCanUseTool && effectivePermissionPromptToolName) {
1311
1418
  throw new Error(
1312
1419
  "canUseTool requires streamingInput mode ('auto' or 'always') and cannot be used with permissionPromptToolName (SDK constraint). Set streamingInput: 'auto' (or 'always') and remove permissionPromptToolName, or remove canUseTool."
1313
1420
  );
@@ -1315,11 +1422,11 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1315
1422
  const sdkPrompt = wantsStreamInput ? toAsyncIterablePrompt(
1316
1423
  messagesPrompt,
1317
1424
  outputStreamEnded,
1318
- this.settings.resume ?? this.sessionId,
1425
+ effectiveResume,
1319
1426
  streamingContentParts
1320
1427
  ) : messagesPrompt;
1321
1428
  this.logger.debug(
1322
- `[claude-code] Starting stream query with streamingInput: ${wantsStreamInput}, session: ${this.settings.resume ?? this.sessionId ?? "new"}`
1429
+ `[claude-code] Starting stream query with streamingInput: ${wantsStreamInput}, session: ${effectiveResume ?? "new"}`
1323
1430
  );
1324
1431
  const response = (0, import_claude_agent_sdk.query)({
1325
1432
  prompt: sdkPrompt,
@@ -1590,6 +1697,10 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1590
1697
  }
1591
1698
  } else if (message.type === "result") {
1592
1699
  done();
1700
+ if ("is_error" in message && message.is_error === true) {
1701
+ const errorMessage = "result" in message && typeof message.result === "string" ? message.result : "Claude Code CLI returned an error";
1702
+ throw Object.assign(new Error(errorMessage), { exitCode: 1 });
1703
+ }
1593
1704
  if (message.subtype === "error_max_structured_output_retries") {
1594
1705
  throw new Error(
1595
1706
  "Failed to generate valid structured output after maximum retries. The model could not produce a response matching the required schema."
@@ -1748,7 +1859,7 @@ var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
1748
1859
  if (isAbortError(error)) {
1749
1860
  errorToEmit = options.abortSignal?.aborted ? options.abortSignal.reason : error;
1750
1861
  } else {
1751
- errorToEmit = this.handleClaudeCodeError(error, messagesPrompt);
1862
+ errorToEmit = this.handleClaudeCodeError(error, messagesPrompt, collectedStderr);
1752
1863
  }
1753
1864
  controller.enqueue({
1754
1865
  type: "error",