@clinebot/llms 0.0.6 → 0.0.7

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.
@@ -5,6 +5,7 @@
5
5
  * Supports prompt caching, extended thinking, and native tool calling.
6
6
  */
7
7
  import { Anthropic } from "@anthropic-ai/sdk";
8
+ import type { RawMessageStreamEvent } from "@anthropic-ai/sdk/resources";
8
9
  import { type ApiStream, type HandlerModelInfo, type ProviderConfig } from "../types";
9
10
  import type { Message, ToolDefinition } from "../types/messages";
10
11
  import { BaseHandler } from "./base";
@@ -18,7 +19,16 @@ export declare class AnthropicHandler extends BaseHandler {
18
19
  getMessages(_systemPrompt: string, messages: Message[]): Anthropic.MessageParam[];
19
20
  createMessage(systemPrompt: string, messages: Message[], tools?: ToolDefinition[]): ApiStream;
20
21
  private createMessageInternal;
21
- private processChunk;
22
+ protected processChunk(chunk: RawMessageStreamEvent, currentToolCall: {
23
+ id: string;
24
+ name: string;
25
+ arguments: string;
26
+ }, usageSnapshot: {
27
+ inputTokens: number;
28
+ outputTokens: number;
29
+ cacheReadTokens: number;
30
+ cacheWriteTokens: number;
31
+ }, responseId: string): Generator<import("../types").ApiStreamChunk>;
22
32
  }
23
33
  /**
24
34
  * Create an Anthropic handler
@@ -103,6 +103,8 @@ export interface Message {
103
103
  export interface MessageWithMetadata extends Message {
104
104
  /** Unique message ID */
105
105
  id?: string;
106
+ /** Additional message metadata for storage/history consumers */
107
+ metadata?: Record<string, unknown>;
106
108
  /** Provider ID used to generate this message */
107
109
  providerId?: string;
108
110
  /** Model ID used to generate this message */
@@ -11,7 +11,7 @@ interface ToolCallDelta {
11
11
  id?: string;
12
12
  function?: {
13
13
  name?: string;
14
- arguments?: string;
14
+ arguments?: unknown;
15
15
  };
16
16
  }
17
17
  /**
@@ -31,4 +31,5 @@ export declare class ToolCallProcessor {
31
31
  * Reset the processor state
32
32
  */
33
33
  reset(): void;
34
+ private normalizeArgumentsDelta;
34
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clinebot/llms",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Config-driven SDK for selecting, extending, and instantiating LLM providers and models",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -14,7 +14,7 @@ export const GENERATED_PROVIDER_MODELS: {
14
14
  version: number;
15
15
  providers: Record<string, Record<string, ModelInfo>>;
16
16
  } = {
17
- version: 1773975012193,
17
+ version: 1774031158558,
18
18
  providers: {
19
19
  aihubmix: {
20
20
  "claude-sonnet-4-6": {
@@ -9235,6 +9235,20 @@ export const GENERATED_PROVIDER_MODELS: {
9235
9235
  },
9236
9236
  releaseDate: "2026-03-18",
9237
9237
  },
9238
+ "xiaomi/mimo-v2-pro": {
9239
+ id: "xiaomi/mimo-v2-pro",
9240
+ name: "MiMo V2 Pro",
9241
+ contextWindow: 1000000,
9242
+ maxTokens: 128000,
9243
+ capabilities: ["tools", "reasoning", "temperature"],
9244
+ pricing: {
9245
+ input: 1,
9246
+ output: 3,
9247
+ cacheRead: 0.19999999999999998,
9248
+ cacheWrite: 0,
9249
+ },
9250
+ releaseDate: "2026-03-18",
9251
+ },
9238
9252
  "openai/gpt-5.4-mini": {
9239
9253
  id: "openai/gpt-5.4-mini",
9240
9254
  name: "GPT 5.4 Mini",
@@ -7,10 +7,26 @@
7
7
 
8
8
  import { getGeneratedModelsForProvider } from "../generated-access";
9
9
  import type { ModelCollection, ModelInfo } from "../schemas/index";
10
- import { OPENAI_MODELS } from "./openai";
10
+
11
+ function removeCustomToolCapability(model: ModelInfo): ModelInfo {
12
+ if (!model.capabilities?.includes("tools")) {
13
+ return model;
14
+ }
15
+
16
+ return {
17
+ ...model,
18
+ capabilities: model.capabilities.filter(
19
+ (capability) => capability !== "tools",
20
+ ),
21
+ };
22
+ }
11
23
 
12
24
  export const OPENAI_CODEX_MODELS: Record<string, ModelInfo> =
13
- getGeneratedModelsForProvider("openai");
25
+ Object.fromEntries(
26
+ Object.entries(getGeneratedModelsForProvider("openai")).map(
27
+ ([modelId, model]) => [modelId, removeCustomToolCapability(model)],
28
+ ),
29
+ );
14
30
 
15
31
  export const OPENAI_CODEX_DEFAULT_MODEL =
16
32
  Object.keys(OPENAI_CODEX_MODELS)[0] ?? "gpt-5.3-codex";
@@ -26,5 +42,5 @@ export const OPENAI_CODEX_PROVIDER: ModelCollection = {
26
42
  defaultModelId: OPENAI_CODEX_DEFAULT_MODEL,
27
43
  capabilities: ["reasoning", "oauth"],
28
44
  },
29
- models: OPENAI_MODELS,
45
+ models: OPENAI_CODEX_MODELS,
30
46
  };
@@ -203,7 +203,7 @@ export class AnthropicHandler extends BaseHandler {
203
203
  yield { type: "done", success: true, id: responseId };
204
204
  }
205
205
 
206
- private *processChunk(
206
+ protected *processChunk(
207
207
  chunk: RawMessageStreamEvent,
208
208
  currentToolCall: { id: string; name: string; arguments: string },
209
209
  usageSnapshot: {
@@ -332,11 +332,13 @@ export class AnthropicHandler extends BaseHandler {
332
332
  case "content_block_stop": {
333
333
  // If we have a tool call, yield it
334
334
  if (currentToolCall.id) {
335
- let parsedArgs: Record<string, unknown>;
335
+ let parsedArgs: string | Record<string, unknown>;
336
336
  try {
337
337
  parsedArgs = JSON.parse(currentToolCall.arguments || "{}");
338
338
  } catch {
339
- parsedArgs = {};
339
+ // Preserve the raw JSON fragment so downstream can classify it
340
+ // as an invalid tool call instead of silently turning it into {}.
341
+ parsedArgs = currentToolCall.arguments;
340
342
  }
341
343
 
342
344
  yield {
@@ -1,4 +1,5 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { OPENAI_CODEX_PROVIDER } from "../../models/providers/openai-codex";
2
3
  import { CodexHandler } from "./community-sdk";
3
4
 
4
5
  const streamTextSpy = vi.fn();
@@ -120,4 +121,40 @@ describe("CodexHandler", () => {
120
121
  "sk-test-key",
121
122
  );
122
123
  });
124
+
125
+ it("does not surface Codex native tool calls as local tool calls", async () => {
126
+ streamTextSpy.mockReturnValue({
127
+ fullStream: makeStreamParts([
128
+ {
129
+ type: "tool-call",
130
+ toolCallId: "codex-call-1",
131
+ toolName: "read_file",
132
+ args: { path: "README.md" },
133
+ },
134
+ {
135
+ type: "finish",
136
+ usage: { inputTokens: 8, outputTokens: 3 },
137
+ },
138
+ ]),
139
+ });
140
+
141
+ const handler = new CodexHandler({
142
+ providerId: "openai-codex",
143
+ modelId: "gpt-5.3-codex",
144
+ });
145
+
146
+ const chunks: Array<Record<string, unknown>> = [];
147
+ for await (const chunk of handler.createMessage("System", [
148
+ { role: "user", content: "Hi" },
149
+ ])) {
150
+ chunks.push(chunk as unknown as Record<string, unknown>);
151
+ }
152
+
153
+ expect(chunks.map((chunk) => chunk.type)).toEqual(["usage", "done"]);
154
+ });
155
+
156
+ it("does not advertise custom tool capability for Codex models", () => {
157
+ const model = OPENAI_CODEX_PROVIDER.models["gpt-5.3-codex"];
158
+ expect(model?.capabilities).not.toContain("tools");
159
+ });
123
160
  });
@@ -145,7 +145,6 @@ export class CodexHandler extends AiSdkProviderHandler {
145
145
  > {
146
146
  return {
147
147
  reasoningTypes: ["reasoning-delta", "reasoning"],
148
- enableToolCalls: true,
149
148
  toolCallArgsOrder: ["args", "input"],
150
149
  };
151
150
  }
@@ -121,6 +121,8 @@ export interface Message {
121
121
  export interface MessageWithMetadata extends Message {
122
122
  /** Unique message ID */
123
123
  id?: string;
124
+ /** Additional message metadata for storage/history consumers */
125
+ metadata?: Record<string, unknown>;
124
126
  /** Provider ID used to generate this message */
125
127
  providerId?: string;
126
128
  /** Model ID used to generate this message */
@@ -33,6 +33,66 @@ describe("ToolCallProcessor", () => {
33
33
  expect(second[0].tool_call.function.arguments).toBe(' -la"]}');
34
34
  });
35
35
 
36
+ it("normalizes cumulative argument snapshots into deltas", () => {
37
+ const processor = new ToolCallProcessor();
38
+
39
+ const first = processor.processToolCallDeltas(
40
+ [
41
+ {
42
+ index: 0,
43
+ id: "call_1",
44
+ function: { name: "editor", arguments: '{"command":"create"' },
45
+ },
46
+ ],
47
+ "resp_1",
48
+ );
49
+
50
+ const second = processor.processToolCallDeltas(
51
+ [
52
+ {
53
+ index: 0,
54
+ function: {
55
+ arguments: '{"command":"create","path":"/tmp/file.txt"}',
56
+ },
57
+ },
58
+ ],
59
+ "resp_1",
60
+ );
61
+
62
+ expect(first).toHaveLength(1);
63
+ expect(second).toHaveLength(1);
64
+ expect(first[0].tool_call.function.arguments).toBe('{"command":"create"');
65
+ expect(second[0].tool_call.function.arguments).toBe(
66
+ ',"path":"/tmp/file.txt"}',
67
+ );
68
+ });
69
+
70
+ it("serializes object-shaped arguments instead of concatenating [object Object]", () => {
71
+ const processor = new ToolCallProcessor();
72
+
73
+ const result = processor.processToolCallDeltas(
74
+ [
75
+ {
76
+ index: 0,
77
+ id: "call_1",
78
+ function: {
79
+ name: "editor",
80
+ arguments: {
81
+ command: "create",
82
+ path: "/tmp/file.txt",
83
+ },
84
+ },
85
+ },
86
+ ],
87
+ "resp_1",
88
+ );
89
+
90
+ expect(result).toHaveLength(1);
91
+ expect(result[0].tool_call.function.arguments).toBe(
92
+ '{"command":"create","path":"/tmp/file.txt"}',
93
+ );
94
+ });
95
+
36
96
  it("preserves tool call id/name for interleaved parallel deltas", () => {
37
97
  const processor = new ToolCallProcessor();
38
98
 
@@ -18,7 +18,7 @@ interface ToolCallDelta {
18
18
  id?: string;
19
19
  function?: {
20
20
  name?: string;
21
- arguments?: string;
21
+ arguments?: unknown;
22
22
  };
23
23
  }
24
24
 
@@ -61,7 +61,11 @@ export class ToolCallProcessor {
61
61
  if (fn?.name) {
62
62
  toolCall.name = fn.name;
63
63
  }
64
- const deltaArguments = fn?.arguments ?? "";
64
+ const rawArguments = fn?.arguments;
65
+ const deltaArguments = this.normalizeArgumentsDelta(
66
+ toolCall.arguments,
67
+ rawArguments,
68
+ );
65
69
  if (deltaArguments) {
66
70
  toolCall.arguments += deltaArguments;
67
71
  }
@@ -108,4 +112,35 @@ export class ToolCallProcessor {
108
112
  reset(): void {
109
113
  this.toolCalls.clear();
110
114
  }
115
+
116
+ private normalizeArgumentsDelta(
117
+ accumulatedArguments: string,
118
+ rawArguments: unknown,
119
+ ): string {
120
+ if (rawArguments == null) {
121
+ return "";
122
+ }
123
+
124
+ const nextArguments =
125
+ typeof rawArguments === "string"
126
+ ? rawArguments
127
+ : JSON.stringify(rawArguments);
128
+
129
+ if (!nextArguments) {
130
+ return "";
131
+ }
132
+
133
+ // Some OpenAI-compatible providers emit cumulative argument snapshots
134
+ // instead of true deltas. Convert those snapshots back into a suffix so
135
+ // downstream accumulation only happens once.
136
+ if (
137
+ accumulatedArguments &&
138
+ nextArguments.length >= accumulatedArguments.length &&
139
+ nextArguments.startsWith(accumulatedArguments)
140
+ ) {
141
+ return nextArguments.slice(accumulatedArguments.length);
142
+ }
143
+
144
+ return nextArguments;
145
+ }
111
146
  }