@opencow-ai/opencow-agent-sdk 0.4.2-beta.1 → 0.4.3

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/dist/cli.mjs CHANGED
@@ -84200,6 +84200,22 @@ function sanitizeTypeField(record2) {
84200
84200
  record2.type = filtered;
84201
84201
  }
84202
84202
  }
84203
+ function makeSchemaNullable(schema) {
84204
+ if ("enum" in schema || "const" in schema)
84205
+ return schema;
84206
+ const raw = schema.type;
84207
+ if (typeof raw === "string") {
84208
+ if (raw === "null")
84209
+ return schema;
84210
+ return { ...schema, type: [raw, "null"] };
84211
+ }
84212
+ if (Array.isArray(raw)) {
84213
+ if (raw.includes("null"))
84214
+ return schema;
84215
+ return { ...schema, type: [...raw, "null"] };
84216
+ }
84217
+ return schema;
84218
+ }
84203
84219
  function sanitizeSchemaForOpenAICompat(schema) {
84204
84220
  const stripped = stripSchemaKeywords(schema, OPENAI_INCOMPATIBLE_SCHEMA_KEYWORDS);
84205
84221
  if (!isSchemaRecord(stripped)) {
@@ -84516,7 +84532,7 @@ function convertAnthropicMessagesToResponsesInput(messages) {
84516
84532
  }
84517
84533
  return items.filter((item) => item.type !== "message" || item.content.length > 0);
84518
84534
  }
84519
- function enforceStrictSchema(schema) {
84535
+ function enforceStrictSchema(schema, topLevel = true) {
84520
84536
  const record2 = sanitizeSchemaForOpenAICompat(schema);
84521
84537
  if (record2.format === "uri") {
84522
84538
  delete record2.format;
@@ -84525,10 +84541,10 @@ function enforceStrictSchema(schema) {
84525
84541
  record2.additionalProperties = false;
84526
84542
  if (record2.properties && typeof record2.properties === "object" && !Array.isArray(record2.properties)) {
84527
84543
  const props = record2.properties;
84528
- const allKeys = Object.keys(props);
84544
+ const originalRequired = Array.isArray(record2.required) ? record2.required.filter((key) => typeof key === "string") : [];
84529
84545
  const enforcedProps = {};
84530
84546
  for (const [key, value] of Object.entries(props)) {
84531
- const strictValue = enforceStrictSchema(value);
84547
+ const strictValue = enforceStrictSchema(value, false);
84532
84548
  if (strictValue && typeof strictValue === "object" && strictValue.type === "object" && strictValue.additionalProperties === false && (!strictValue.properties || Object.keys(strictValue.properties).length === 0)) {
84533
84549
  continue;
84534
84550
  }
@@ -84536,20 +84552,27 @@ function enforceStrictSchema(schema) {
84536
84552
  }
84537
84553
  record2.properties = enforcedProps;
84538
84554
  record2.required = Object.keys(enforcedProps);
84555
+ if (topLevel) {
84556
+ for (const key of Object.keys(enforcedProps)) {
84557
+ if (!originalRequired.includes(key)) {
84558
+ enforcedProps[key] = makeSchemaNullable(enforcedProps[key]);
84559
+ }
84560
+ }
84561
+ }
84539
84562
  } else {
84540
84563
  record2.required = [];
84541
84564
  }
84542
84565
  }
84543
84566
  if ("items" in record2) {
84544
84567
  if (Array.isArray(record2.items)) {
84545
- record2.items = record2.items.map((item) => enforceStrictSchema(item));
84568
+ record2.items = record2.items.map((item) => enforceStrictSchema(item, false));
84546
84569
  } else {
84547
- record2.items = enforceStrictSchema(record2.items);
84570
+ record2.items = enforceStrictSchema(record2.items, false);
84548
84571
  }
84549
84572
  }
84550
84573
  for (const key of ["anyOf", "oneOf", "allOf"]) {
84551
84574
  if (key in record2 && Array.isArray(record2[key])) {
84552
- record2[key] = record2[key].map((item) => enforceStrictSchema(item));
84575
+ record2[key] = record2[key].map((item) => enforceStrictSchema(item, false));
84553
84576
  }
84554
84577
  }
84555
84578
  return record2;
@@ -85241,34 +85264,41 @@ function convertMessages(messages, system) {
85241
85264
  }
85242
85265
  return result;
85243
85266
  }
85244
- function normalizeSchemaForOpenAI(schema, strict = true) {
85267
+ function normalizeSchemaForOpenAI(schema, strict = true, topLevel = true) {
85245
85268
  const record2 = sanitizeSchemaForOpenAICompat(schema);
85246
85269
  if (record2.type === "object" && record2.properties) {
85247
85270
  const properties = record2.properties;
85248
85271
  const existingRequired = Array.isArray(record2.required) ? record2.required : [];
85249
85272
  const normalizedProps = {};
85250
85273
  for (const [key, value] of Object.entries(properties)) {
85251
- normalizedProps[key] = normalizeSchemaForOpenAI(value, strict);
85274
+ normalizedProps[key] = normalizeSchemaForOpenAI(value, strict, false);
85252
85275
  }
85253
85276
  record2.properties = normalizedProps;
85254
85277
  if (strict) {
85255
85278
  const allKeys = Object.keys(normalizedProps);
85256
85279
  record2.required = Array.from(new Set([...existingRequired, ...allKeys]));
85257
85280
  record2.additionalProperties = false;
85281
+ if (topLevel) {
85282
+ for (const key of allKeys) {
85283
+ if (!existingRequired.includes(key)) {
85284
+ normalizedProps[key] = makeSchemaNullable(normalizedProps[key]);
85285
+ }
85286
+ }
85287
+ }
85258
85288
  } else {
85259
85289
  record2.required = existingRequired.filter((k) => (k in normalizedProps));
85260
85290
  }
85261
85291
  }
85262
85292
  if ("items" in record2) {
85263
85293
  if (Array.isArray(record2.items)) {
85264
- record2.items = record2.items.map((item) => normalizeSchemaForOpenAI(item, strict));
85294
+ record2.items = record2.items.map((item) => normalizeSchemaForOpenAI(item, strict, false));
85265
85295
  } else {
85266
- record2.items = normalizeSchemaForOpenAI(record2.items, strict);
85296
+ record2.items = normalizeSchemaForOpenAI(record2.items, strict, false);
85267
85297
  }
85268
85298
  }
85269
85299
  for (const key of ["anyOf", "oneOf", "allOf"]) {
85270
85300
  if (key in record2 && Array.isArray(record2[key])) {
85271
- record2[key] = record2[key].map((item) => normalizeSchemaForOpenAI(item, strict));
85301
+ record2[key] = record2[key].map((item) => normalizeSchemaForOpenAI(item, strict, false));
85272
85302
  }
85273
85303
  }
85274
85304
  return record2;
@@ -94171,7 +94201,7 @@ function printStartupScreen() {
94171
94201
  const sLen = ` ● ${sL} Ready — type /help to begin`.length;
94172
94202
  out.push(boxRow(sRow, W2, sLen));
94173
94203
  out.push(`${rgb(...BORDER)}╚${"═".repeat(W2 - 2)}╝${RESET}`);
94174
- out.push(` ${DIM}${rgb(...DIMCOL)}opencow ${RESET}${rgb(...ACCENT)}v${"0.4.2-beta.1"}${RESET}`);
94204
+ out.push(` ${DIM}${rgb(...DIMCOL)}opencow ${RESET}${rgb(...ACCENT)}v${"0.4.3"}${RESET}`);
94175
94205
  out.push("");
94176
94206
  process.stdout.write(out.join(`
94177
94207
  `) + `
@@ -120565,7 +120595,7 @@ var init_FileReadTool = __esm(() => {
120565
120595
  },
120566
120596
  renderToolUseErrorMessage,
120567
120597
  async validateInput({ file_path, pages }, toolUseContext) {
120568
- if (pages !== undefined) {
120598
+ if (typeof pages === "string" && pages.trim() !== "") {
120569
120599
  const parsed = parsePDFPageRange(pages);
120570
120600
  if (!parsed) {
120571
120601
  return {
@@ -120614,7 +120644,8 @@ var init_FileReadTool = __esm(() => {
120614
120644
  }
120615
120645
  return { result: true };
120616
120646
  },
120617
- async call({ file_path, offset = 1, limit = undefined, pages }, context3, _canUseTool, parentMessage) {
120647
+ async call({ file_path, offset = 1, limit = undefined, pages: rawPages }, context3, _canUseTool, parentMessage) {
120648
+ const pages = typeof rawPages === "string" && rawPages.trim() !== "" ? rawPages : undefined;
120618
120649
  const { readFileState, fileReadingLimits } = context3;
120619
120650
  const defaults2 = getDefaultFileReadingLimits();
120620
120651
  const maxSizeBytes = fileReadingLimits?.maxSizeBytes ?? defaults2.maxSizeBytes;
@@ -239449,6 +239480,30 @@ var init_permissionLogging = __esm(() => {
239449
239480
  CODE_EDITING_TOOLS = ["Edit", "Write", "NotebookEdit"];
239450
239481
  });
239451
239482
 
239483
+ // src/lib/toolInputNullCoercion.ts
239484
+ function omitNullProps(input) {
239485
+ const out = {};
239486
+ for (const [key, value] of Object.entries(input)) {
239487
+ if (value !== null)
239488
+ out[key] = value;
239489
+ }
239490
+ return out;
239491
+ }
239492
+ function safeParseToolInputWithNullCoercion(schema, input) {
239493
+ const first = schema.safeParse(input);
239494
+ if (first.success)
239495
+ return first;
239496
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
239497
+ return first;
239498
+ }
239499
+ const record3 = input;
239500
+ const hasNull = Object.values(record3).some((value) => value === null);
239501
+ if (!hasNull)
239502
+ return first;
239503
+ const retry = schema.safeParse(omitNullProps(record3));
239504
+ return retry.success ? retry : first;
239505
+ }
239506
+
239452
239507
  // src/constants/tools.ts
239453
239508
  var ALL_AGENT_DISALLOWED_TOOLS, CUSTOM_AGENT_DISALLOWED_TOOLS, ASYNC_AGENT_ALLOWED_TOOLS, IN_PROCESS_TEAMMATE_ALLOWED_TOOLS, COORDINATOR_MODE_ALLOWED_TOOLS;
239454
239509
  var init_tools = __esm(() => {
@@ -240552,7 +240607,7 @@ function streamedCheckPermissionsAndCallTool(tool, toolUseID, input, toolUseCont
240552
240607
  return stream4;
240553
240608
  }
240554
240609
  async function checkPermissionsAndCallTool(tool, toolUseID, input, toolUseContext, canUseTool, assistantMessage, messageId, requestId, mcpServerType, mcpServerBaseUrl, onToolProgress) {
240555
- const parsedInput = tool.inputSchema.safeParse(input);
240610
+ const parsedInput = safeParseToolInputWithNullCoercion(tool.inputSchema, input);
240556
240611
  if (!parsedInput.success) {
240557
240612
  let errorContent = formatZodValidationError(tool.name, parsedInput.error);
240558
240613
  const schemaHint = buildSchemaNotSentHint(tool, toolUseContext.messages, toolUseContext.options.tools);
@@ -243937,7 +243992,7 @@ function getAnthropicEnvMetadata() {
243937
243992
  function getBuildAgeMinutes() {
243938
243993
  if (false)
243939
243994
  ;
243940
- const buildTime = new Date("2026-05-19T13:08:43.722Z").getTime();
243995
+ const buildTime = new Date("2026-05-25T12:12:24.294Z").getTime();
243941
243996
  if (isNaN(buildTime))
243942
243997
  return;
243943
243998
  return Math.floor((Date.now() - buildTime) / 60000);
@@ -479076,7 +479131,7 @@ function buildPrimarySection() {
479076
479131
  }, undefined, false, undefined, this);
479077
479132
  return [{
479078
479133
  label: "Version",
479079
- value: "0.4.2-beta.1"
479134
+ value: "0.4.3"
479080
479135
  }, {
479081
479136
  label: "Session name",
479082
479137
  value: nameValue
@@ -535394,7 +535449,7 @@ var init_bridge_kick = __esm(() => {
535394
535449
  var call58 = async () => {
535395
535450
  return {
535396
535451
  type: "text",
535397
- value: `${"99.0.0"} (built ${"2026-05-19T13:08:43.722Z"})`
535452
+ value: `${"99.0.0"} (built ${"2026-05-25T12:12:24.294Z"})`
535398
535453
  };
535399
535454
  }, version2, version_default;
535400
535455
  var init_version = __esm(() => {
@@ -557504,7 +557559,7 @@ function WelcomeV2() {
557504
557559
  dimColor: true,
557505
557560
  children: [
557506
557561
  "v",
557507
- "0.4.2-beta.1",
557562
+ "0.4.3",
557508
557563
  " "
557509
557564
  ]
557510
557565
  }, undefined, true, undefined, this)
@@ -557704,7 +557759,7 @@ function WelcomeV2() {
557704
557759
  dimColor: true,
557705
557760
  children: [
557706
557761
  "v",
557707
- "0.4.2-beta.1",
557762
+ "0.4.3",
557708
557763
  " "
557709
557764
  ]
557710
557765
  }, undefined, true, undefined, this)
@@ -557930,7 +557985,7 @@ function AppleTerminalWelcomeV2(t0) {
557930
557985
  dimColor: true,
557931
557986
  children: [
557932
557987
  "v",
557933
- "0.4.2-beta.1",
557988
+ "0.4.3",
557934
557989
  " "
557935
557990
  ]
557936
557991
  }, undefined, true, undefined, this);
@@ -558184,7 +558239,7 @@ function AppleTerminalWelcomeV2(t0) {
558184
558239
  dimColor: true,
558185
558240
  children: [
558186
558241
  "v",
558187
- "0.4.2-beta.1",
558242
+ "0.4.3",
558188
558243
  " "
558189
558244
  ]
558190
558245
  }, undefined, true, undefined, this);
@@ -579030,7 +579085,7 @@ Usage: claude --remote "your task description"`, () => gracefulShutdown(1));
579030
579085
  pendingHookMessages
579031
579086
  }, renderAndRun);
579032
579087
  }
579033
- }).version("0.4.2-beta.1 (OpenCow)", "-v, --version", "Output the version number");
579088
+ }).version("0.4.3 (OpenCow)", "-v, --version", "Output the version number");
579034
579089
  program2.option("-w, --worktree [name]", "Create a new git worktree for this session (optionally specify a name)");
579035
579090
  program2.option("--tmux", "Create a tmux session for the worktree (requires --worktree). Uses iTerm2 native panes when available; use --tmux=classic for traditional tmux.");
579036
579091
  if (canUserConfigureAdvisor()) {
@@ -579676,7 +579731,7 @@ if (false) {}
579676
579731
  async function main2() {
579677
579732
  const args = process.argv.slice(2);
579678
579733
  if (args.length === 1 && (args[0] === "--version" || args[0] === "-v" || args[0] === "-V")) {
579679
- console.log(`${"0.4.2-beta.1"} (OpenCow)`);
579734
+ console.log(`${"0.4.3"} (OpenCow)`);
579680
579735
  return;
579681
579736
  }
579682
579737
  if (args.includes("--provider")) {
@@ -579794,4 +579849,4 @@ async function main2() {
579794
579849
  }
579795
579850
  main2();
579796
579851
 
579797
- //# debugId=522047064233364464756E2164756E21
579852
+ //# debugId=29A0EB81D23B627164756E2164756E21
package/dist/client.js CHANGED
@@ -94949,6 +94949,22 @@ function sanitizeTypeField(record3) {
94949
94949
  record3.type = filtered;
94950
94950
  }
94951
94951
  }
94952
+ function makeSchemaNullable(schema) {
94953
+ if ("enum" in schema || "const" in schema)
94954
+ return schema;
94955
+ const raw = schema.type;
94956
+ if (typeof raw === "string") {
94957
+ if (raw === "null")
94958
+ return schema;
94959
+ return { ...schema, type: [raw, "null"] };
94960
+ }
94961
+ if (Array.isArray(raw)) {
94962
+ if (raw.includes("null"))
94963
+ return schema;
94964
+ return { ...schema, type: [...raw, "null"] };
94965
+ }
94966
+ return schema;
94967
+ }
94952
94968
  function sanitizeSchemaForOpenAICompat(schema) {
94953
94969
  const stripped = stripSchemaKeywords(schema, OPENAI_INCOMPATIBLE_SCHEMA_KEYWORDS);
94954
94970
  if (!isSchemaRecord(stripped)) {
@@ -95265,7 +95281,7 @@ function convertAnthropicMessagesToResponsesInput(messages) {
95265
95281
  }
95266
95282
  return items.filter((item) => item.type !== "message" || item.content.length > 0);
95267
95283
  }
95268
- function enforceStrictSchema(schema) {
95284
+ function enforceStrictSchema(schema, topLevel = true) {
95269
95285
  const record3 = sanitizeSchemaForOpenAICompat(schema);
95270
95286
  if (record3.format === "uri") {
95271
95287
  delete record3.format;
@@ -95274,10 +95290,10 @@ function enforceStrictSchema(schema) {
95274
95290
  record3.additionalProperties = false;
95275
95291
  if (record3.properties && typeof record3.properties === "object" && !Array.isArray(record3.properties)) {
95276
95292
  const props = record3.properties;
95277
- const allKeys = Object.keys(props);
95293
+ const originalRequired = Array.isArray(record3.required) ? record3.required.filter((key) => typeof key === "string") : [];
95278
95294
  const enforcedProps = {};
95279
95295
  for (const [key, value] of Object.entries(props)) {
95280
- const strictValue = enforceStrictSchema(value);
95296
+ const strictValue = enforceStrictSchema(value, false);
95281
95297
  if (strictValue && typeof strictValue === "object" && strictValue.type === "object" && strictValue.additionalProperties === false && (!strictValue.properties || Object.keys(strictValue.properties).length === 0)) {
95282
95298
  continue;
95283
95299
  }
@@ -95285,20 +95301,27 @@ function enforceStrictSchema(schema) {
95285
95301
  }
95286
95302
  record3.properties = enforcedProps;
95287
95303
  record3.required = Object.keys(enforcedProps);
95304
+ if (topLevel) {
95305
+ for (const key of Object.keys(enforcedProps)) {
95306
+ if (!originalRequired.includes(key)) {
95307
+ enforcedProps[key] = makeSchemaNullable(enforcedProps[key]);
95308
+ }
95309
+ }
95310
+ }
95288
95311
  } else {
95289
95312
  record3.required = [];
95290
95313
  }
95291
95314
  }
95292
95315
  if ("items" in record3) {
95293
95316
  if (Array.isArray(record3.items)) {
95294
- record3.items = record3.items.map((item) => enforceStrictSchema(item));
95317
+ record3.items = record3.items.map((item) => enforceStrictSchema(item, false));
95295
95318
  } else {
95296
- record3.items = enforceStrictSchema(record3.items);
95319
+ record3.items = enforceStrictSchema(record3.items, false);
95297
95320
  }
95298
95321
  }
95299
95322
  for (const key of ["anyOf", "oneOf", "allOf"]) {
95300
95323
  if (key in record3 && Array.isArray(record3[key])) {
95301
- record3[key] = record3[key].map((item) => enforceStrictSchema(item));
95324
+ record3[key] = record3[key].map((item) => enforceStrictSchema(item, false));
95302
95325
  }
95303
95326
  }
95304
95327
  return record3;
@@ -96065,34 +96088,41 @@ function convertMessages(messages, system) {
96065
96088
  }
96066
96089
  return result;
96067
96090
  }
96068
- function normalizeSchemaForOpenAI(schema, strict = true) {
96091
+ function normalizeSchemaForOpenAI(schema, strict = true, topLevel = true) {
96069
96092
  const record3 = sanitizeSchemaForOpenAICompat(schema);
96070
96093
  if (record3.type === "object" && record3.properties) {
96071
96094
  const properties = record3.properties;
96072
96095
  const existingRequired = Array.isArray(record3.required) ? record3.required : [];
96073
96096
  const normalizedProps = {};
96074
96097
  for (const [key, value] of Object.entries(properties)) {
96075
- normalizedProps[key] = normalizeSchemaForOpenAI(value, strict);
96098
+ normalizedProps[key] = normalizeSchemaForOpenAI(value, strict, false);
96076
96099
  }
96077
96100
  record3.properties = normalizedProps;
96078
96101
  if (strict) {
96079
96102
  const allKeys = Object.keys(normalizedProps);
96080
96103
  record3.required = Array.from(new Set([...existingRequired, ...allKeys]));
96081
96104
  record3.additionalProperties = false;
96105
+ if (topLevel) {
96106
+ for (const key of allKeys) {
96107
+ if (!existingRequired.includes(key)) {
96108
+ normalizedProps[key] = makeSchemaNullable(normalizedProps[key]);
96109
+ }
96110
+ }
96111
+ }
96082
96112
  } else {
96083
96113
  record3.required = existingRequired.filter((k) => (k in normalizedProps));
96084
96114
  }
96085
96115
  }
96086
96116
  if ("items" in record3) {
96087
96117
  if (Array.isArray(record3.items)) {
96088
- record3.items = record3.items.map((item) => normalizeSchemaForOpenAI(item, strict));
96118
+ record3.items = record3.items.map((item) => normalizeSchemaForOpenAI(item, strict, false));
96089
96119
  } else {
96090
- record3.items = normalizeSchemaForOpenAI(record3.items, strict);
96120
+ record3.items = normalizeSchemaForOpenAI(record3.items, strict, false);
96091
96121
  }
96092
96122
  }
96093
96123
  for (const key of ["anyOf", "oneOf", "allOf"]) {
96094
96124
  if (key in record3 && Array.isArray(record3[key])) {
96095
- record3[key] = record3[key].map((item) => normalizeSchemaForOpenAI(item, strict));
96125
+ record3[key] = record3[key].map((item) => normalizeSchemaForOpenAI(item, strict, false));
96096
96126
  }
96097
96127
  }
96098
96128
  return record3;
@@ -223607,6 +223637,30 @@ var init_permissionLogging = __esm(() => {
223607
223637
  CODE_EDITING_TOOLS = ["Edit", "Write", "NotebookEdit"];
223608
223638
  });
223609
223639
 
223640
+ // src/lib/toolInputNullCoercion.ts
223641
+ function omitNullProps(input) {
223642
+ const out = {};
223643
+ for (const [key, value] of Object.entries(input)) {
223644
+ if (value !== null)
223645
+ out[key] = value;
223646
+ }
223647
+ return out;
223648
+ }
223649
+ function safeParseToolInputWithNullCoercion(schema, input) {
223650
+ const first = schema.safeParse(input);
223651
+ if (first.success)
223652
+ return first;
223653
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
223654
+ return first;
223655
+ }
223656
+ const record3 = input;
223657
+ const hasNull = Object.values(record3).some((value) => value === null);
223658
+ if (!hasNull)
223659
+ return first;
223660
+ const retry = schema.safeParse(omitNullProps(record3));
223661
+ return retry.success ? retry : first;
223662
+ }
223663
+
223610
223664
  // src/constants/tools.ts
223611
223665
  var ALL_AGENT_DISALLOWED_TOOLS, CUSTOM_AGENT_DISALLOWED_TOOLS, ASYNC_AGENT_ALLOWED_TOOLS, IN_PROCESS_TEAMMATE_ALLOWED_TOOLS, COORDINATOR_MODE_ALLOWED_TOOLS;
223612
223666
  var init_tools = __esm(() => {
@@ -279452,7 +279506,7 @@ function streamedCheckPermissionsAndCallTool(tool, toolUseID, input, toolUseCont
279452
279506
  return stream4;
279453
279507
  }
279454
279508
  async function checkPermissionsAndCallTool(tool, toolUseID, input, toolUseContext, canUseTool, assistantMessage, messageId, requestId, mcpServerType, mcpServerBaseUrl, onToolProgress) {
279455
- const parsedInput = tool.inputSchema.safeParse(input);
279509
+ const parsedInput = safeParseToolInputWithNullCoercion(tool.inputSchema, input);
279456
279510
  if (!parsedInput.success) {
279457
279511
  let errorContent = formatZodValidationError(tool.name, parsedInput.error);
279458
279512
  const schemaHint = buildSchemaNotSentHint(tool, toolUseContext.messages, toolUseContext.options.tools);
@@ -282034,7 +282088,7 @@ function getAnthropicEnvMetadata() {
282034
282088
  function getBuildAgeMinutes() {
282035
282089
  if (false)
282036
282090
  ;
282037
- const buildTime = new Date("2026-05-19T13:08:43.722Z").getTime();
282091
+ const buildTime = new Date("2026-05-25T12:12:24.294Z").getTime();
282038
282092
  if (isNaN(buildTime))
282039
282093
  return;
282040
282094
  return Math.floor((Date.now() - buildTime) / 60000);
@@ -285786,7 +285840,7 @@ var init_FileReadTool = __esm(() => {
285786
285840
  },
285787
285841
  renderToolUseErrorMessage: renderToolUseErrorMessage9,
285788
285842
  async validateInput({ file_path, pages }, toolUseContext) {
285789
- if (pages !== undefined) {
285843
+ if (typeof pages === "string" && pages.trim() !== "") {
285790
285844
  const parsed = parsePDFPageRange(pages);
285791
285845
  if (!parsed) {
285792
285846
  return {
@@ -285835,7 +285889,8 @@ var init_FileReadTool = __esm(() => {
285835
285889
  }
285836
285890
  return { result: true };
285837
285891
  },
285838
- async call({ file_path, offset = 1, limit = undefined, pages }, context4, _canUseTool, parentMessage) {
285892
+ async call({ file_path, offset = 1, limit = undefined, pages: rawPages }, context4, _canUseTool, parentMessage) {
285893
+ const pages = typeof rawPages === "string" && rawPages.trim() !== "" ? rawPages : undefined;
285839
285894
  const { readFileState, fileReadingLimits } = context4;
285840
285895
  const defaults2 = getDefaultFileReadingLimits();
285841
285896
  const maxSizeBytes = fileReadingLimits?.maxSizeBytes ?? defaults2.maxSizeBytes;
@@ -335213,4 +335268,4 @@ export {
335213
335268
  AbortError2 as AbortError
335214
335269
  };
335215
335270
 
335216
- //# debugId=058F2C64E659372F64756E2164756E21
335271
+ //# debugId=5554484DB05DB0BC64756E2164756E21
@@ -1,3 +1,18 @@
1
+ /**
2
+ * 把一个属性 schema 标记为「可空」。用于 OpenAI/Codex strict 模式下被强制列入
3
+ * `required` 的「原本可选」字段:strict 模式要求每个属性都出现在 `required`,
4
+ * 否则 400。这会逼模型给本应省略的可选字段编造一个占位值(典型即空串 `""`),
5
+ * 下游再因这个空值报错(issue #79:Read 的 `pages: ""` → Invalid pages parameter;
6
+ * issue #77:gpt 用 read 工具频繁出错)。让字段可空后,模型可以用 `null` 明确表达
7
+ * 「未提供」,再由入参解析层把 `null` 还原为缺省。
8
+ *
9
+ * 仅处理能安全增补的形态,限制 blast radius:
10
+ * - 具名 `type`(string/number/...)→ 追加 `"null"`
11
+ * - `type` 数组 → 追加 `"null"`
12
+ * 带 `enum`/`const` 的字段保持不动(避免 type 允许 null 而枚举/常量不含 null 的
13
+ * 自相矛盾);仅含 anyOf/oneOf 等组合子或无明确 `type` 的字段也保持不动(零回归)。
14
+ */
15
+ export declare function makeSchemaNullable(schema: Record<string, unknown>): Record<string, unknown>;
1
16
  /**
2
17
  * Sanitize JSON Schema into a shape OpenAI-compatible providers and Codex
3
18
  * strict-mode tooling are more likely to accept. This strips provider-rejected
@@ -0,0 +1,20 @@
1
+ import type { z } from 'zod/v4';
2
+ /**
3
+ * 解析工具入参,并对 strict provider 把可选字段强制为 nullable 后模型回传的 `null`
4
+ * 做兜底还原。
5
+ *
6
+ * 背景:OpenAI/Codex strict 模式要求每个属性都出现在 `required`,可选字段会被
7
+ * `makeSchemaNullable` 标成可空(见 lib/schemaSanitizer.ts),模型遂用 `null` 表达
8
+ * 「未提供」。但工具的 Zod inputSchema 多用 `.optional()`(不接受 `null`),若直接
9
+ * `safeParse` 会得到 InputValidationError —— 这正是「① 让 wire schema 可空」必须搭配的
10
+ * 入参侧兜底(issue #79/#77)。
11
+ *
12
+ * 策略:先按原始 schema 解析;仅当「解析失败 且 顶层含 null 值」时,去掉这些 null 键
13
+ * 再解析一次,且只有重试成功才采用重试结果。由此:
14
+ * - 真正接受 null 的 `.nullable()` 字段:原始即可解析 → 不触发重试 → `null` 保留;
15
+ * - 与 null 无关的真实校验错误:去 null 后仍失败 → 返回原始错误,不掩盖;
16
+ * - 可选非空字段被强制塞入的 `null`:去掉后变缺省,`.optional()` 通过。
17
+ *
18
+ * 仅处理顶层 null,与 `makeSchemaNullable` 仅顶层可空的范围对齐。
19
+ */
20
+ export declare function safeParseToolInputWithNullCoercion<T extends z.ZodType>(schema: T, input: unknown): ReturnType<T['safeParse']>;
@@ -1 +1 @@
1
- export { sanitizeSchemaForOpenAICompat } from '../../lib/schemaSanitizer.js';
1
+ export { sanitizeSchemaForOpenAICompat, makeSchemaNullable, } from '../../lib/schemaSanitizer.js';
package/dist/sdk.js CHANGED
@@ -94949,6 +94949,22 @@ function sanitizeTypeField(record3) {
94949
94949
  record3.type = filtered;
94950
94950
  }
94951
94951
  }
94952
+ function makeSchemaNullable(schema) {
94953
+ if ("enum" in schema || "const" in schema)
94954
+ return schema;
94955
+ const raw = schema.type;
94956
+ if (typeof raw === "string") {
94957
+ if (raw === "null")
94958
+ return schema;
94959
+ return { ...schema, type: [raw, "null"] };
94960
+ }
94961
+ if (Array.isArray(raw)) {
94962
+ if (raw.includes("null"))
94963
+ return schema;
94964
+ return { ...schema, type: [...raw, "null"] };
94965
+ }
94966
+ return schema;
94967
+ }
94952
94968
  function sanitizeSchemaForOpenAICompat(schema) {
94953
94969
  const stripped = stripSchemaKeywords(schema, OPENAI_INCOMPATIBLE_SCHEMA_KEYWORDS);
94954
94970
  if (!isSchemaRecord(stripped)) {
@@ -95265,7 +95281,7 @@ function convertAnthropicMessagesToResponsesInput(messages) {
95265
95281
  }
95266
95282
  return items.filter((item) => item.type !== "message" || item.content.length > 0);
95267
95283
  }
95268
- function enforceStrictSchema(schema) {
95284
+ function enforceStrictSchema(schema, topLevel = true) {
95269
95285
  const record3 = sanitizeSchemaForOpenAICompat(schema);
95270
95286
  if (record3.format === "uri") {
95271
95287
  delete record3.format;
@@ -95274,10 +95290,10 @@ function enforceStrictSchema(schema) {
95274
95290
  record3.additionalProperties = false;
95275
95291
  if (record3.properties && typeof record3.properties === "object" && !Array.isArray(record3.properties)) {
95276
95292
  const props = record3.properties;
95277
- const allKeys = Object.keys(props);
95293
+ const originalRequired = Array.isArray(record3.required) ? record3.required.filter((key) => typeof key === "string") : [];
95278
95294
  const enforcedProps = {};
95279
95295
  for (const [key, value] of Object.entries(props)) {
95280
- const strictValue = enforceStrictSchema(value);
95296
+ const strictValue = enforceStrictSchema(value, false);
95281
95297
  if (strictValue && typeof strictValue === "object" && strictValue.type === "object" && strictValue.additionalProperties === false && (!strictValue.properties || Object.keys(strictValue.properties).length === 0)) {
95282
95298
  continue;
95283
95299
  }
@@ -95285,20 +95301,27 @@ function enforceStrictSchema(schema) {
95285
95301
  }
95286
95302
  record3.properties = enforcedProps;
95287
95303
  record3.required = Object.keys(enforcedProps);
95304
+ if (topLevel) {
95305
+ for (const key of Object.keys(enforcedProps)) {
95306
+ if (!originalRequired.includes(key)) {
95307
+ enforcedProps[key] = makeSchemaNullable(enforcedProps[key]);
95308
+ }
95309
+ }
95310
+ }
95288
95311
  } else {
95289
95312
  record3.required = [];
95290
95313
  }
95291
95314
  }
95292
95315
  if ("items" in record3) {
95293
95316
  if (Array.isArray(record3.items)) {
95294
- record3.items = record3.items.map((item) => enforceStrictSchema(item));
95317
+ record3.items = record3.items.map((item) => enforceStrictSchema(item, false));
95295
95318
  } else {
95296
- record3.items = enforceStrictSchema(record3.items);
95319
+ record3.items = enforceStrictSchema(record3.items, false);
95297
95320
  }
95298
95321
  }
95299
95322
  for (const key of ["anyOf", "oneOf", "allOf"]) {
95300
95323
  if (key in record3 && Array.isArray(record3[key])) {
95301
- record3[key] = record3[key].map((item) => enforceStrictSchema(item));
95324
+ record3[key] = record3[key].map((item) => enforceStrictSchema(item, false));
95302
95325
  }
95303
95326
  }
95304
95327
  return record3;
@@ -96065,34 +96088,41 @@ function convertMessages(messages, system) {
96065
96088
  }
96066
96089
  return result;
96067
96090
  }
96068
- function normalizeSchemaForOpenAI(schema, strict = true) {
96091
+ function normalizeSchemaForOpenAI(schema, strict = true, topLevel = true) {
96069
96092
  const record3 = sanitizeSchemaForOpenAICompat(schema);
96070
96093
  if (record3.type === "object" && record3.properties) {
96071
96094
  const properties = record3.properties;
96072
96095
  const existingRequired = Array.isArray(record3.required) ? record3.required : [];
96073
96096
  const normalizedProps = {};
96074
96097
  for (const [key, value] of Object.entries(properties)) {
96075
- normalizedProps[key] = normalizeSchemaForOpenAI(value, strict);
96098
+ normalizedProps[key] = normalizeSchemaForOpenAI(value, strict, false);
96076
96099
  }
96077
96100
  record3.properties = normalizedProps;
96078
96101
  if (strict) {
96079
96102
  const allKeys = Object.keys(normalizedProps);
96080
96103
  record3.required = Array.from(new Set([...existingRequired, ...allKeys]));
96081
96104
  record3.additionalProperties = false;
96105
+ if (topLevel) {
96106
+ for (const key of allKeys) {
96107
+ if (!existingRequired.includes(key)) {
96108
+ normalizedProps[key] = makeSchemaNullable(normalizedProps[key]);
96109
+ }
96110
+ }
96111
+ }
96082
96112
  } else {
96083
96113
  record3.required = existingRequired.filter((k) => (k in normalizedProps));
96084
96114
  }
96085
96115
  }
96086
96116
  if ("items" in record3) {
96087
96117
  if (Array.isArray(record3.items)) {
96088
- record3.items = record3.items.map((item) => normalizeSchemaForOpenAI(item, strict));
96118
+ record3.items = record3.items.map((item) => normalizeSchemaForOpenAI(item, strict, false));
96089
96119
  } else {
96090
- record3.items = normalizeSchemaForOpenAI(record3.items, strict);
96120
+ record3.items = normalizeSchemaForOpenAI(record3.items, strict, false);
96091
96121
  }
96092
96122
  }
96093
96123
  for (const key of ["anyOf", "oneOf", "allOf"]) {
96094
96124
  if (key in record3 && Array.isArray(record3[key])) {
96095
- record3[key] = record3[key].map((item) => normalizeSchemaForOpenAI(item, strict));
96125
+ record3[key] = record3[key].map((item) => normalizeSchemaForOpenAI(item, strict, false));
96096
96126
  }
96097
96127
  }
96098
96128
  return record3;
@@ -223607,6 +223637,30 @@ var init_permissionLogging = __esm(() => {
223607
223637
  CODE_EDITING_TOOLS = ["Edit", "Write", "NotebookEdit"];
223608
223638
  });
223609
223639
 
223640
+ // src/lib/toolInputNullCoercion.ts
223641
+ function omitNullProps(input) {
223642
+ const out = {};
223643
+ for (const [key, value] of Object.entries(input)) {
223644
+ if (value !== null)
223645
+ out[key] = value;
223646
+ }
223647
+ return out;
223648
+ }
223649
+ function safeParseToolInputWithNullCoercion(schema, input) {
223650
+ const first = schema.safeParse(input);
223651
+ if (first.success)
223652
+ return first;
223653
+ if (input === null || typeof input !== "object" || Array.isArray(input)) {
223654
+ return first;
223655
+ }
223656
+ const record3 = input;
223657
+ const hasNull = Object.values(record3).some((value) => value === null);
223658
+ if (!hasNull)
223659
+ return first;
223660
+ const retry = schema.safeParse(omitNullProps(record3));
223661
+ return retry.success ? retry : first;
223662
+ }
223663
+
223610
223664
  // src/constants/tools.ts
223611
223665
  var ALL_AGENT_DISALLOWED_TOOLS, CUSTOM_AGENT_DISALLOWED_TOOLS, ASYNC_AGENT_ALLOWED_TOOLS, IN_PROCESS_TEAMMATE_ALLOWED_TOOLS, COORDINATOR_MODE_ALLOWED_TOOLS;
223612
223666
  var init_tools = __esm(() => {
@@ -279452,7 +279506,7 @@ function streamedCheckPermissionsAndCallTool(tool, toolUseID, input, toolUseCont
279452
279506
  return stream4;
279453
279507
  }
279454
279508
  async function checkPermissionsAndCallTool(tool, toolUseID, input, toolUseContext, canUseTool, assistantMessage, messageId, requestId, mcpServerType, mcpServerBaseUrl, onToolProgress) {
279455
- const parsedInput = tool.inputSchema.safeParse(input);
279509
+ const parsedInput = safeParseToolInputWithNullCoercion(tool.inputSchema, input);
279456
279510
  if (!parsedInput.success) {
279457
279511
  let errorContent = formatZodValidationError(tool.name, parsedInput.error);
279458
279512
  const schemaHint = buildSchemaNotSentHint(tool, toolUseContext.messages, toolUseContext.options.tools);
@@ -282034,7 +282088,7 @@ function getAnthropicEnvMetadata() {
282034
282088
  function getBuildAgeMinutes() {
282035
282089
  if (false)
282036
282090
  ;
282037
- const buildTime = new Date("2026-05-19T13:08:43.722Z").getTime();
282091
+ const buildTime = new Date("2026-05-25T12:12:24.294Z").getTime();
282038
282092
  if (isNaN(buildTime))
282039
282093
  return;
282040
282094
  return Math.floor((Date.now() - buildTime) / 60000);
@@ -285786,7 +285840,7 @@ var init_FileReadTool = __esm(() => {
285786
285840
  },
285787
285841
  renderToolUseErrorMessage: renderToolUseErrorMessage9,
285788
285842
  async validateInput({ file_path, pages }, toolUseContext) {
285789
- if (pages !== undefined) {
285843
+ if (typeof pages === "string" && pages.trim() !== "") {
285790
285844
  const parsed = parsePDFPageRange(pages);
285791
285845
  if (!parsed) {
285792
285846
  return {
@@ -285835,7 +285889,8 @@ var init_FileReadTool = __esm(() => {
285835
285889
  }
285836
285890
  return { result: true };
285837
285891
  },
285838
- async call({ file_path, offset = 1, limit = undefined, pages }, context4, _canUseTool, parentMessage) {
285892
+ async call({ file_path, offset = 1, limit = undefined, pages: rawPages }, context4, _canUseTool, parentMessage) {
285893
+ const pages = typeof rawPages === "string" && rawPages.trim() !== "" ? rawPages : undefined;
285839
285894
  const { readFileState, fileReadingLimits } = context4;
285840
285895
  const defaults2 = getDefaultFileReadingLimits();
285841
285896
  const maxSizeBytes = fileReadingLimits?.maxSizeBytes ?? defaults2.maxSizeBytes;
@@ -335213,4 +335268,4 @@ export {
335213
335268
  AbortError2 as AbortError
335214
335269
  };
335215
335270
 
335216
- //# debugId=62113B6684CF32A564756E2164756E21
335271
+ //# debugId=532222AFD20CE11464756E2164756E21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencow-ai/opencow-agent-sdk",
3
- "version": "0.4.2-beta.1",
3
+ "version": "0.4.3",
4
4
  "description": "Claude Code opened to any LLM — OpenAI, Gemini, DeepSeek, Ollama, and 200+ models",
5
5
  "type": "module",
6
6
  "main": "./dist/sdk.js",