@opencode-ai/ai 0.0.0-dev-18164 → 0.0.0-dev-18171

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.
@@ -471,7 +471,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
471
471
  });
472
472
  const lowerOptions = (request) => {
473
473
  const options = OpenResponsesOptions.resolve(request);
474
- const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
474
+ const cacheKey = ProviderShared.promptCacheKey(request);
475
475
  const parallelToolCalls = resolveParallelToolCalls(request);
476
476
  return {
477
477
  ...(options.instructions ? { instructions: options.instructions } : {}),
@@ -517,7 +517,7 @@ const detectZaiToolStream = (provider, baseURL, modelID) => {
517
517
  };
518
518
  const lowerOptions = (request, supportsStore) => {
519
519
  const options = OpenAIOptions.resolve(request);
520
- const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
520
+ const cacheKey = ProviderShared.promptCacheKey(request);
521
521
  return {
522
522
  ...(supportsStore && options.store !== undefined ? { store: options.store } : {}),
523
523
  // For providers that support `store`, ensure stateless `store:false` is sent
@@ -11,7 +11,7 @@ export declare const JsonObject: Schema.$Record<Schema.String, Schema.Unknown>;
11
11
  export declare const optionalArray: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.$Array<S>>;
12
12
  export declare const optionalNull: <const S extends Schema.Top>(schema: S) => Schema.optional<Schema.NullOr<S>>;
13
13
  export declare const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
14
- export declare const clampPromptCacheKey: (key: string | undefined) => string | undefined;
14
+ export declare const promptCacheKey: (request: LLMRequest) => string | undefined;
15
15
  /**
16
16
  * Streaming tool-call accumulator. Adapters that build a tool call across
17
17
  * multiple `tool-input-delta` chunks store the partial JSON input string here
@@ -16,12 +16,12 @@ export const optionalNull = (schema) => Schema.optional(Schema.NullOr(schema));
16
16
  export const OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH = 64;
17
17
  // OpenAI limits `prompt_cache_key` to 64 chars; DeepSeek and Zai inherit the same
18
18
  // limit via their OpenAI-compatible APIs. Clamp with unicode-aware slicing.
19
- export const clampPromptCacheKey = (key) => {
20
- if (key === undefined)
19
+ export const promptCacheKey = (request) => {
20
+ if (request.cache === "none" || request.promptCacheKey === undefined)
21
21
  return undefined;
22
- const chars = Array.from(key);
22
+ const chars = Array.from(request.promptCacheKey);
23
23
  if (chars.length <= OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH)
24
- return key;
24
+ return request.promptCacheKey;
25
25
  return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join("");
26
26
  };
27
27
  /**
@@ -43,8 +43,58 @@ export function parseJSON(jsonString, allowPartial = Allow.ALL) {
43
43
  return decodeJson(input);
44
44
  }
45
45
  catch { }
46
- return _parseJSON(input, allowPartial);
46
+ const repaired = repairJSON(input);
47
+ if (repaired !== input) {
48
+ try {
49
+ return decodeJson(repaired);
50
+ }
51
+ catch { }
52
+ }
53
+ try {
54
+ return _parseJSON(input, allowPartial);
55
+ }
56
+ catch (error) {
57
+ if (repaired !== input)
58
+ return _parseJSON(repaired, allowPartial);
59
+ throw error;
60
+ }
47
61
  }
62
+ const repairJSON = (input) => {
63
+ let repaired = "";
64
+ let quoted = false;
65
+ for (let index = 0; index < input.length; index++) {
66
+ const character = input[index];
67
+ if (!quoted) {
68
+ repaired += character;
69
+ if (character === '"')
70
+ quoted = true;
71
+ continue;
72
+ }
73
+ if (character === '"') {
74
+ repaired += character;
75
+ quoted = false;
76
+ continue;
77
+ }
78
+ if (character === "\\") {
79
+ const next = input[index + 1];
80
+ if (next === "u" && /^[0-9a-fA-F]{4}$/.test(input.slice(index + 2, index + 6))) {
81
+ repaired += input.slice(index, index + 6);
82
+ index += 5;
83
+ continue;
84
+ }
85
+ if (next !== undefined && '"\\/bfnrtu'.includes(next)) {
86
+ repaired += `\\${next}`;
87
+ index++;
88
+ continue;
89
+ }
90
+ repaired += "\\\\";
91
+ continue;
92
+ }
93
+ const code = character.charCodeAt(0);
94
+ repaired += code <= 0x1f ? `\\u${code.toString(16).padStart(4, "0")}` : character;
95
+ }
96
+ return repaired;
97
+ };
48
98
  const _parseJSON = (jsonString, allow) => {
49
99
  const length = jsonString.length;
50
100
  let index = 0;
@@ -138,7 +188,12 @@ const _parseJSON = (jsonString, allow) => {
138
188
  skipBlank();
139
189
  index++;
140
190
  try {
141
- object[key] = parseAny();
191
+ Object.defineProperty(object, key, {
192
+ value: parseAny(),
193
+ enumerable: true,
194
+ configurable: true,
195
+ writable: true,
196
+ });
142
197
  }
143
198
  catch (error) {
144
199
  if (Allow.OBJ & allow)
@@ -61,7 +61,7 @@ export declare const appendOrStart: <K extends StreamKey>(route: string, tools:
61
61
  export declare const appendExisting: <K extends StreamKey>(route: string, tools: State<K>, key: K, text: string, missingToolMessage: string) => AppendOutcome<K> | AIError;
62
62
  /**
63
63
  * Finalize one pending tool call: parse the accumulated raw JSON, remove it
64
- * from state, and return either a call or a non-executable local input error.
64
+ * from state, and recover incomplete local arguments when needed.
65
65
  * Missing keys are a no-op because some providers emit stop events for
66
66
  * non-tool content blocks.
67
67
  */
@@ -19,34 +19,28 @@ const inputStart = (tool) => LLMEvent.toolInputStart({
19
19
  providerExecuted: tool.providerExecuted ? true : undefined,
20
20
  providerMetadata: tool.providerMetadata,
21
21
  });
22
- const inputDelta = (tool, text) => {
23
- const input = parsePartialInput(tool.input);
24
- return LLMEvent.toolInputDelta({
25
- id: tool.id,
26
- name: tool.name,
27
- text,
28
- ...(Option.isSome(input) ? { input: input.value } : {}),
29
- });
30
- };
22
+ const inputDelta = (tool, text) => LLMEvent.toolInputDelta({
23
+ id: tool.id,
24
+ name: tool.name,
25
+ text,
26
+ input: Option.getOrElse(parsePartialInput(tool.input), () => ({})),
27
+ });
31
28
  const toolCall = (route, tool, inputOverride) => {
32
29
  const raw = inputOverride ?? tool.input;
33
- return parseToolInput(route, tool.name, raw).pipe(Effect.map((input) => LLMEvent.toolCall({
30
+ return parseToolInput(route, tool.name, raw).pipe(Effect.catch((error) => tool.providerExecuted
31
+ ? Effect.fail(error)
32
+ : Effect.succeed(Option.getOrElse(Option.map(parsePartialInput(raw), (input) => input ?? {}), () => ({})))), Effect.map((input) => LLMEvent.toolCall({
34
33
  id: tool.id,
35
34
  name: tool.name,
36
35
  input,
37
36
  providerExecuted: tool.providerExecuted ? true : undefined,
38
37
  providerMetadata: tool.providerMetadata,
39
- })), Effect.catch((error) => tool.providerExecuted
40
- ? Effect.fail(error)
41
- : Effect.succeed(LLMEvent.toolInputError({
42
- id: tool.id,
43
- name: tool.name,
44
- raw,
45
- }))));
38
+ })));
46
39
  };
47
- const finishEvents = (tool, event) => event.type === "tool-input-error"
48
- ? [event]
49
- : [LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }), event];
40
+ const finishEvents = (tool, event) => [
41
+ LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
42
+ event,
43
+ ];
50
44
  /** Store the updated tool and produce the optional public delta event. */
51
45
  const appendTool = (tools, key, tool, text) => {
52
46
  const events = [];
@@ -105,7 +99,7 @@ export const appendExisting = (route, tools, key, text, missingToolMessage) => {
105
99
  };
106
100
  /**
107
101
  * Finalize one pending tool call: parse the accumulated raw JSON, remove it
108
- * from state, and return either a call or a non-executable local input error.
102
+ * from state, and recover incomplete local arguments when needed.
109
103
  * Missing keys are a no-op because some providers emit stop events for
110
104
  * non-tool content blocks.
111
105
  */
@@ -8,7 +8,7 @@ import { ProviderID } from "../schema/index.js";
8
8
  import * as OpenAICompatibleProfiles from "./openai-compatible-profile.js";
9
9
  import * as OpenAIChat from "../protocols/openai-chat.js";
10
10
  import { newBreakpoints, ttlBucket } from "../protocols/utils/cache.js";
11
- import { isRecord, ProviderShared } from "../protocols/shared.js";
11
+ import { isRecord } from "../protocols/shared.js";
12
12
  export const profile = OpenAICompatibleProfiles.profiles.openrouter;
13
13
  export const id = ProviderID.make(profile.provider);
14
14
  const ADAPTER = "openrouter";
@@ -39,12 +39,10 @@ export const protocol = Protocol.make({
39
39
  reasoning_details: reasoningDetails,
40
40
  };
41
41
  });
42
- const cacheKey = ProviderShared.clampPromptCacheKey(request.promptCacheKey);
43
42
  return {
44
43
  ...body,
45
44
  messages,
46
45
  ...bodyOptions(request.providerOptions),
47
- ...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
48
46
  };
49
47
  })),
50
48
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "0.0.0-dev-18164",
3
+ "version": "0.0.0-dev-18171",
4
4
  "name": "@opencode-ai/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@clack/prompts": "1.0.0-alpha.1",
32
32
  "@effect/platform-node": "4.0.0-rc.111",
33
- "@opencode-ai/http-recorder": "0.0.0-dev-18164",
33
+ "@opencode-ai/http-recorder": "0.0.0-dev-18171",
34
34
  "@tsconfig/bun": "1.0.9",
35
35
  "@types/bun": "1.3.13",
36
36
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@smithy/eventstream-codec": "4.2.14",
41
41
  "@smithy/util-utf8": "4.2.2",
42
- "@opencode-ai/schema": "0.0.0-dev-18164",
42
+ "@opencode-ai/schema": "0.0.0-dev-18171",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.111",
45
45
  "google-auth-library": "10.5.0"