@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.
- package/dist/index.browser.js +1 -1
- package/dist/index.js +12 -12
- package/dist/providers/handlers/anthropic-base.d.ts +11 -1
- package/dist/providers/types/messages.d.ts +2 -0
- package/dist/providers/utils/tool-processor.d.ts +2 -1
- package/package.json +1 -1
- package/src/models/generated.ts +15 -1
- package/src/models/providers/openai-codex.ts +19 -3
- package/src/providers/handlers/anthropic-base.ts +5 -3
- package/src/providers/handlers/codex.test.ts +37 -0
- package/src/providers/handlers/community-sdk.ts +0 -1
- package/src/providers/types/messages.ts +2 -0
- package/src/providers/utils/tool-processor.test.ts +60 -0
- package/src/providers/utils/tool-processor.ts +37 -2
|
@@ -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
|
-
|
|
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?:
|
|
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
package/src/models/generated.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|
|
@@ -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?:
|
|
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
|
|
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
|
}
|