@tyvm/knowhow 0.0.151 → 0.0.154
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/package.json +1 -1
- package/src/agents/base/base.ts +31 -5
- package/src/agents/tools/list.ts +31 -28
- package/src/auth/keyManager.ts +168 -3
- package/src/auth/registerIdentity.ts +1 -1
- package/src/clients/anthropic.ts +3 -1
- package/src/clients/contextLimits.ts +3 -1
- package/src/clients/gemini.ts +16 -4
- package/src/clients/http.ts +8 -5
- package/src/clients/knowhowMcp.ts +2 -2
- package/src/clients/openai.ts +7 -4
- package/src/clients/pricing/google.ts +11 -2
- package/src/clients/pricing/xai.ts +5 -1
- package/src/clients/xai.ts +6 -3
- package/src/commands/misc.ts +25 -2
- package/tests/clients/reasoningEffort.test.ts +5 -1
- package/tests/unit/auth/keyManager.test.ts +63 -0
- package/tests/unit/commands/misc.test.ts +85 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +1 -1
- package/ts_build/src/agents/base/base.js +13 -5
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +0 -26
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/auth/keyManager.d.ts +4 -0
- package/ts_build/src/auth/keyManager.js +138 -3
- package/ts_build/src/auth/keyManager.js.map +1 -1
- package/ts_build/src/auth/registerIdentity.js +1 -1
- package/ts_build/src/auth/registerIdentity.js.map +1 -1
- package/ts_build/src/clients/anthropic.js +3 -1
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +2 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/gemini.js +14 -3
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/http.js +8 -5
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/knowhowMcp.js +2 -2
- package/ts_build/src/clients/knowhowMcp.js.map +1 -1
- package/ts_build/src/clients/openai.js +7 -4
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/google.d.ts +1 -0
- package/ts_build/src/clients/pricing/google.js +7 -0
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +1 -0
- package/ts_build/src/clients/pricing/xai.js +4 -0
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/xai.js +6 -3
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/misc.js +21 -3
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/types.d.ts +2 -0
- package/ts_build/tests/clients/reasoningEffort.test.js +2 -1
- package/ts_build/tests/clients/reasoningEffort.test.js.map +1 -1
- package/ts_build/tests/unit/auth/keyManager.test.js +44 -0
- package/ts_build/tests/unit/auth/keyManager.test.js.map +1 -1
- package/ts_build/tests/unit/commands/misc.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/misc.test.js +70 -0
- package/ts_build/tests/unit/commands/misc.test.js.map +1 -0
package/package.json
CHANGED
package/src/agents/base/base.ts
CHANGED
|
@@ -1107,6 +1107,14 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1107
1107
|
|
|
1108
1108
|
// Typically, there's only one choice in the array, but you could have many
|
|
1109
1109
|
// If you set `n` to more than 1, you will get multiple choices
|
|
1110
|
+
// Collect all tool calls across all choices up front.
|
|
1111
|
+
// This is used by detectTruncatedToolCalls so that the size heuristic
|
|
1112
|
+
// compares the full completion_tokens against ALL argument content,
|
|
1113
|
+
// rather than just the current choice's single tool call (Anthropic maps
|
|
1114
|
+
// each parallel tool_use block to a separate choice).
|
|
1115
|
+
const allResponseToolCalls = response.choices.flatMap(
|
|
1116
|
+
(c) => c.message.tool_calls ?? []
|
|
1117
|
+
);
|
|
1110
1118
|
for (const choice of response.choices) {
|
|
1111
1119
|
messages.push(choice.message);
|
|
1112
1120
|
|
|
@@ -1136,7 +1144,8 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1136
1144
|
|
|
1137
1145
|
const truncationWarning = this.detectTruncatedToolCalls(
|
|
1138
1146
|
toolCalls,
|
|
1139
|
-
response
|
|
1147
|
+
response,
|
|
1148
|
+
allResponseToolCalls
|
|
1140
1149
|
);
|
|
1141
1150
|
if (truncationWarning) {
|
|
1142
1151
|
messages.push(truncationWarning as Message);
|
|
@@ -1589,11 +1598,14 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1589
1598
|
* 2. The model reported many output tokens but the total argument content received is tiny
|
|
1590
1599
|
* relative to what those tokens should represent (soft/silent truncation).
|
|
1591
1600
|
*
|
|
1601
|
+
* @param toolCalls - The tool calls from the CURRENT choice being checked.
|
|
1602
|
+
* @param allToolCalls - ALL tool calls across ALL choices in the response (used for the size heuristic).
|
|
1592
1603
|
* Returns a warning system message if truncation is detected, or null otherwise.
|
|
1593
1604
|
*/
|
|
1594
1605
|
detectTruncatedToolCalls(
|
|
1595
1606
|
toolCalls: ToolCall[],
|
|
1596
|
-
response: CompletionResponse
|
|
1607
|
+
response: CompletionResponse,
|
|
1608
|
+
allToolCalls?: ToolCall[]
|
|
1597
1609
|
): { role: string; content: string } | null {
|
|
1598
1610
|
// Subtract thinking/reasoning tokens — they're billed as output tokens but
|
|
1599
1611
|
// don't produce visible argument content, so including them inflates the heuristic.
|
|
@@ -1602,7 +1614,13 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1602
1614
|
(response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
|
|
1603
1615
|
(response?.usage?.output_tokens_details?.reasoning_tokens ?? 0);
|
|
1604
1616
|
const outputTokens = Math.max(0, rawOutputTokens - thinkingTokens);
|
|
1605
|
-
|
|
1617
|
+
// Use allToolCalls for the size heuristic so that parallel tool calls from
|
|
1618
|
+
// Anthropic (each mapped to a separate choice) are counted together. Without
|
|
1619
|
+
// this, a response with N parallel tool calls would compare the full
|
|
1620
|
+
// completion_tokens count against only 1/N of the actual argument content,
|
|
1621
|
+
// causing false-positive truncation warnings.
|
|
1622
|
+
const callsForSizeCheck = allToolCalls ?? toolCalls;
|
|
1623
|
+
const totalArgLength = callsForSizeCheck.reduce(
|
|
1606
1624
|
(sum, tc) => sum + (tc.function?.arguments?.length || 0),
|
|
1607
1625
|
0
|
|
1608
1626
|
);
|
|
@@ -1688,9 +1706,13 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1688
1706
|
.filter((s): s is string => typeof s === "string" && s.trim().length > 0)
|
|
1689
1707
|
.join("\n\n");
|
|
1690
1708
|
|
|
1691
|
-
|
|
1709
|
+
if (!breakdownContent) {
|
|
1710
|
+
throw new Error("Compaction task-breakdown request returned no text content");
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
this.taskBreakdown = breakdownContent;
|
|
1692
1714
|
this.log(`task breakdown cost: ${response.usd_cost}`);
|
|
1693
|
-
return this.taskBreakdown
|
|
1715
|
+
return this.taskBreakdown;
|
|
1694
1716
|
}
|
|
1695
1717
|
|
|
1696
1718
|
async compressMessages(messages: Message[]) {
|
|
@@ -1742,6 +1764,10 @@ export abstract class BaseAgent implements IAgent {
|
|
|
1742
1764
|
const summaries = response.choices
|
|
1743
1765
|
.map((c) => c.message.content)
|
|
1744
1766
|
.filter((s): s is string => typeof s === "string" && s.trim().length > 0);
|
|
1767
|
+
if (summaries.length === 0) {
|
|
1768
|
+
throw new Error("Compaction summary request returned no text content");
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1745
1771
|
this.summaries.push(...summaries);
|
|
1746
1772
|
|
|
1747
1773
|
const startMessages = [
|
package/src/agents/tools/list.ts
CHANGED
|
@@ -31,34 +31,37 @@ function getPluginNames(): string {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const includedTools = [
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
/*
|
|
35
|
+
* This doesn't use messageProcessors etc, needs rewrite
|
|
36
|
+
*{
|
|
37
|
+
* type: "function",
|
|
38
|
+
* function: {
|
|
39
|
+
* name: "agentCall",
|
|
40
|
+
* description:
|
|
41
|
+
* "Run another registered agent in-process as a blocking call and get its final answer back as a string. Unlike a plain llm() call, the target agent can use tools, loop, and maintain its own context. Useful as a graph node that delegates to a specialized agent (e.g. Researcher, Patcher). For long-running / parallel / detached work prefer startAgentTask.",
|
|
42
|
+
* parameters: {
|
|
43
|
+
* type: "object",
|
|
44
|
+
* positional: true,
|
|
45
|
+
* properties: {
|
|
46
|
+
* agentName: {
|
|
47
|
+
* type: "string",
|
|
48
|
+
* description:
|
|
49
|
+
* "The name of the agent to call (e.g. Researcher, Developer, Patcher).",
|
|
50
|
+
* },
|
|
51
|
+
* query: {
|
|
52
|
+
* type: "string",
|
|
53
|
+
* description: "The query / task to send to the agent.",
|
|
54
|
+
* },
|
|
55
|
+
* },
|
|
56
|
+
* required: ["agentName", "query"],
|
|
57
|
+
* },
|
|
58
|
+
* returns: {
|
|
59
|
+
* type: "string",
|
|
60
|
+
* description: "The agent's final answer text.",
|
|
61
|
+
* },
|
|
62
|
+
* },
|
|
63
|
+
*},
|
|
64
|
+
*/
|
|
62
65
|
{
|
|
63
66
|
type: "function",
|
|
64
67
|
function: {
|
package/src/auth/keyManager.ts
CHANGED
|
@@ -6,6 +6,13 @@ import path from "path";
|
|
|
6
6
|
const ED25519_KEY_TYPE = "ssh-ed25519";
|
|
7
7
|
const ED25519_RAW_KEY_LENGTH = 32;
|
|
8
8
|
const ED25519_SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
|
|
9
|
+
const ED25519_PKCS8_PREFIX = Buffer.from("302e020100300506032b657004220420", "hex");
|
|
10
|
+
|
|
11
|
+
interface OpenSshMetadata {
|
|
12
|
+
encrypted: boolean;
|
|
13
|
+
algorithm?: string;
|
|
14
|
+
seed?: Buffer;
|
|
15
|
+
}
|
|
9
16
|
|
|
10
17
|
export interface KeyPair {
|
|
11
18
|
privateKeyPath: string;
|
|
@@ -90,6 +97,14 @@ export function getOrCreatePublicKey(privateKeyPath?: string): KeyPair {
|
|
|
90
97
|
: generateKeyPair(privateKeyPath);
|
|
91
98
|
}
|
|
92
99
|
|
|
100
|
+
export function ensureIdentityKeyPair(privateKeyPath?: string): {
|
|
101
|
+
keyPair: KeyPair;
|
|
102
|
+
created: boolean;
|
|
103
|
+
} {
|
|
104
|
+
if (keyPairExists(privateKeyPath)) return { keyPair: loadKeyPair(privateKeyPath), created: false };
|
|
105
|
+
return { keyPair: generateKeyPair(privateKeyPath), created: true };
|
|
106
|
+
}
|
|
107
|
+
|
|
93
108
|
export function signMessage(message: string, privateKeyPath?: string): string {
|
|
94
109
|
const privatePath = privateKeyPath ?? getDefaultPrivateKeyPath();
|
|
95
110
|
if (!fs.existsSync(privatePath)) throw new Error(`Private key not found at: ${privatePath}`);
|
|
@@ -98,14 +113,50 @@ export function signMessage(message: string, privateKeyPath?: string): string {
|
|
|
98
113
|
|
|
99
114
|
function readEd25519PrivateKey(privatePath: string): crypto.KeyObject {
|
|
100
115
|
let key: crypto.KeyObject;
|
|
116
|
+
const privateKeyContents = fs.readFileSync(privatePath, "utf8");
|
|
117
|
+
const openSshMetadata = parseOpenSshMetadata(privateKeyContents);
|
|
118
|
+
|
|
119
|
+
if (openSshMetadata?.encrypted) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
`Identity at ${privatePath} is an encrypted OpenSSH key. Use an unencrypted Ed25519 key for CLI auth (example: ssh-keygen -t ed25519 -N '' -f ${privatePath})`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
try {
|
|
102
|
-
key = crypto.createPrivateKey(
|
|
126
|
+
key = crypto.createPrivateKey({ key: privateKeyContents, format: "pem" });
|
|
103
127
|
} catch {
|
|
104
|
-
|
|
128
|
+
if (openSshMetadata) {
|
|
129
|
+
if (openSshMetadata.algorithm && openSshMetadata.algorithm !== ED25519_KEY_TYPE) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Identity at ${privatePath} must be an OpenSSH Ed25519 key (found ${openSshMetadata.algorithm})`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (openSshMetadata.seed) {
|
|
136
|
+
key = createEd25519PrivateKeyFromSeed(openSshMetadata.seed, privatePath);
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Identity at ${privatePath} must be a PKCS#8 Ed25519 private key or an unencrypted OpenSSH Ed25519 private key`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return key;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new Error(`Identity at ${privatePath} must be a PKCS#8 Ed25519 private key or an unencrypted OpenSSH Ed25519 private key`);
|
|
105
146
|
}
|
|
147
|
+
|
|
148
|
+
const detectedAlgorithm = key.asymmetricKeyType === "ed25519" ? openSshMetadata?.algorithm ?? "ed25519" : key.asymmetricKeyType;
|
|
149
|
+
|
|
106
150
|
if (key.asymmetricKeyType !== "ed25519") {
|
|
107
|
-
throw new Error(
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Identity at ${privatePath} must be an Ed25519 private key (found ${detectedAlgorithm ?? "unknown"})`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (openSshMetadata?.algorithm && openSshMetadata.algorithm !== ED25519_KEY_TYPE) {
|
|
157
|
+
throw new Error(`Identity at ${privatePath} must be an OpenSSH Ed25519 key (found ${openSshMetadata.algorithm})`);
|
|
108
158
|
}
|
|
159
|
+
|
|
109
160
|
return key;
|
|
110
161
|
}
|
|
111
162
|
|
|
@@ -164,3 +215,117 @@ function keyPairMetadata(privatePath: string, rawPublicKey: Buffer): KeyPair {
|
|
|
164
215
|
fingerprint: `SHA256:${hash}`,
|
|
165
216
|
};
|
|
166
217
|
}
|
|
218
|
+
|
|
219
|
+
function createEd25519PrivateKeyFromSeed(seed: Buffer, privatePath: string): crypto.KeyObject {
|
|
220
|
+
if (seed.length !== ED25519_RAW_KEY_LENGTH) {
|
|
221
|
+
throw new Error(`Identity at ${privatePath} has an invalid OpenSSH Ed25519 private key payload`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const der = Buffer.concat([ED25519_PKCS8_PREFIX, seed]);
|
|
226
|
+
return crypto.createPrivateKey({ key: der, format: "der", type: "pkcs8" });
|
|
227
|
+
} catch {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Identity at ${privatePath} must be a PKCS#8 Ed25519 private key or an unencrypted OpenSSH Ed25519 private key`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseOpenSshMetadata(privateKeyContents: string): OpenSshMetadata | undefined {
|
|
235
|
+
const trimmed = privateKeyContents.trim();
|
|
236
|
+
if (!trimmed.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----")) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const body = trimmed
|
|
241
|
+
.replace("-----BEGIN OPENSSH PRIVATE KEY-----", "")
|
|
242
|
+
.replace("-----END OPENSSH PRIVATE KEY-----", "")
|
|
243
|
+
.replace(/\s+/g, "");
|
|
244
|
+
|
|
245
|
+
let buffer: Buffer;
|
|
246
|
+
try {
|
|
247
|
+
buffer = Buffer.from(body, "base64");
|
|
248
|
+
} catch {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let offset = 0;
|
|
253
|
+
|
|
254
|
+
const magic = Buffer.from("openssh-key-v1\0", "utf8");
|
|
255
|
+
if (buffer.length < magic.length || !buffer.subarray(0, magic.length).equals(magic)) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
offset += magic.length;
|
|
259
|
+
|
|
260
|
+
const readString = (): string | undefined => {
|
|
261
|
+
if (offset + 4 > buffer.length) return undefined;
|
|
262
|
+
const length = buffer.readUInt32BE(offset);
|
|
263
|
+
offset += 4;
|
|
264
|
+
if (offset + length > buffer.length) return undefined;
|
|
265
|
+
const value = buffer.subarray(offset, offset + length).toString("utf8");
|
|
266
|
+
offset += length;
|
|
267
|
+
return value;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const readBuffer = (): Buffer | undefined => {
|
|
271
|
+
if (offset + 4 > buffer.length) return undefined;
|
|
272
|
+
const length = buffer.readUInt32BE(offset);
|
|
273
|
+
offset += 4;
|
|
274
|
+
if (offset + length > buffer.length) return undefined;
|
|
275
|
+
const value = buffer.subarray(offset, offset + length);
|
|
276
|
+
offset += length;
|
|
277
|
+
return value;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const ciphername = readString();
|
|
281
|
+
const kdfname = readString();
|
|
282
|
+
const kdfoptions = readBuffer();
|
|
283
|
+
if (!ciphername || !kdfname || !kdfoptions) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (offset + 4 > buffer.length) return { encrypted: ciphername !== "none" || kdfname !== "none" };
|
|
288
|
+
const keyCount = buffer.readUInt32BE(offset);
|
|
289
|
+
offset += 4;
|
|
290
|
+
|
|
291
|
+
for (let i = 0; i < keyCount; i++) {
|
|
292
|
+
const skippedPublic = readBuffer();
|
|
293
|
+
if (!skippedPublic) return { encrypted: ciphername !== "none" || kdfname !== "none" };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const privateBlock = readBuffer();
|
|
297
|
+
if (!privateBlock) return { encrypted: ciphername !== "none" || kdfname !== "none" };
|
|
298
|
+
|
|
299
|
+
let privateOffset = 0;
|
|
300
|
+
const readPrivateString = (): string | undefined => {
|
|
301
|
+
if (privateOffset + 4 > privateBlock.length) return undefined;
|
|
302
|
+
const length = privateBlock.readUInt32BE(privateOffset);
|
|
303
|
+
privateOffset += 4;
|
|
304
|
+
if (privateOffset + length > privateBlock.length) return undefined;
|
|
305
|
+
const value = privateBlock.subarray(privateOffset, privateOffset + length).toString("utf8");
|
|
306
|
+
privateOffset += length;
|
|
307
|
+
return value;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const readPrivateBuffer = (): Buffer | undefined => {
|
|
311
|
+
if (privateOffset + 4 > privateBlock.length) return undefined;
|
|
312
|
+
const length = privateBlock.readUInt32BE(privateOffset);
|
|
313
|
+
privateOffset += 4;
|
|
314
|
+
if (privateOffset + length > privateBlock.length) return undefined;
|
|
315
|
+
const value = privateBlock.subarray(privateOffset, privateOffset + length);
|
|
316
|
+
privateOffset += length;
|
|
317
|
+
return value;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
privateOffset += 8; // checkints
|
|
321
|
+
|
|
322
|
+
const algorithm = readPrivateString();
|
|
323
|
+
let seed: Buffer | undefined;
|
|
324
|
+
if (algorithm === ED25519_KEY_TYPE) {
|
|
325
|
+
readPrivateBuffer(); // public key (32 bytes)
|
|
326
|
+
const privateConcat = readPrivateBuffer(); // private seed (32) + public key (32)
|
|
327
|
+
seed = privateConcat?.subarray(0, ED25519_RAW_KEY_LENGTH);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return { encrypted: ciphername !== "none" || kdfname !== "none", algorithm, seed };
|
|
331
|
+
}
|
|
@@ -28,7 +28,7 @@ export function decodeJwtPayload(jwt: string): Record<string, unknown> {
|
|
|
28
28
|
|
|
29
29
|
export function extractOrgIdFromJwt(jwt: string): string {
|
|
30
30
|
const payload = decodeJwtPayload(jwt);
|
|
31
|
-
const orgId = payload
|
|
31
|
+
const orgId = payload.org ?? payload.orgId ?? payload.org_id ?? payload.organizationId;
|
|
32
32
|
if (typeof orgId !== "string" || !orgId) {
|
|
33
33
|
throw new Error("JWT does not contain an organization claim (expected org, orgId, org_id, or organizationId)");
|
|
34
34
|
}
|
package/src/clients/anthropic.ts
CHANGED
|
@@ -472,7 +472,9 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
472
472
|
...(thinkingParam && { thinking: thinkingParam }),
|
|
473
473
|
...(effortOutputConfig && { output_config: effortOutputConfig }),
|
|
474
474
|
...(tools.length && {
|
|
475
|
-
tool_choice: {
|
|
475
|
+
tool_choice: {
|
|
476
|
+
type: options.tool_choice ?? "auto",
|
|
477
|
+
},
|
|
476
478
|
tools,
|
|
477
479
|
}),
|
|
478
480
|
}, { signal: options.signal });
|
|
@@ -123,7 +123,8 @@ export const ContextLimits: Record<string, number> = {
|
|
|
123
123
|
[Models.anthropic.Sonnet4_0]: 200_000,
|
|
124
124
|
|
|
125
125
|
// ─── Google ───────────────────────────────────────────────────────────────
|
|
126
|
-
// Gemini 3.6 / 3.5 (text)
|
|
126
|
+
// Gemini 3.7 / 3.6 / 3.5 (text)
|
|
127
|
+
[Models.google.Gemini_37_Flash]: 1_048_576,
|
|
127
128
|
[Models.google.Gemini_36_Flash]: 1_000_000,
|
|
128
129
|
[Models.google.Gemini_35_Flash]: 1_000_000,
|
|
129
130
|
[Models.google.Gemini_35_Flash_Lite]: 1_000_000,
|
|
@@ -178,6 +179,7 @@ export const ContextLimits: Record<string, number> = {
|
|
|
178
179
|
[Models.google.Gemini_15_Pro]: 2_000_000,
|
|
179
180
|
|
|
180
181
|
// ─── xAI ──────────────────────────────────────────────────────────────────
|
|
182
|
+
[Models.xai.Grok4_6]: 500_000,
|
|
181
183
|
[Models.xai.Grok4_1_Fast_Reasoning]: 2_000_000,
|
|
182
184
|
[Models.xai.Grok4_1_Fast_NonReasoning]: 2_000_000,
|
|
183
185
|
[Models.xai.Grok_4_20_Reasoning]: 131_072,
|
package/src/clients/gemini.ts
CHANGED
|
@@ -420,7 +420,7 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
420
420
|
|
|
421
421
|
/**
|
|
422
422
|
* Builds the thinkingConfig for Gemini models that support it.
|
|
423
|
-
* - Gemini 3.x models use thinkingLevel
|
|
423
|
+
* - Gemini 3.x models use thinkingLevel (supported levels vary by model)
|
|
424
424
|
* - Gemini 2.5 models use thinkingBudget: number (0 = off, -1 = dynamic)
|
|
425
425
|
*
|
|
426
426
|
* Maps CompletionOptions.reasoning_effort to provider-specific values.
|
|
@@ -433,9 +433,11 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
433
433
|
|
|
434
434
|
// Gemini 3.x — use thinkingLevel
|
|
435
435
|
if (GoogleThinkingLevelModels.includes(model)) {
|
|
436
|
+
// Gemini 3.7 Flash rejects "minimal"; low is its least intensive level.
|
|
437
|
+
const minimumLevel = model === Models.google.Gemini_37_Flash ? "low" : "minimal";
|
|
436
438
|
const levelMap: Record<string, string> = {
|
|
437
|
-
none:
|
|
438
|
-
minimal:
|
|
439
|
+
none: minimumLevel,
|
|
440
|
+
minimal: minimumLevel,
|
|
439
441
|
low: "low",
|
|
440
442
|
medium: "medium",
|
|
441
443
|
high: "high",
|
|
@@ -474,6 +476,7 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
474
476
|
const { systemInstruction, contents } = this.transformMessages(
|
|
475
477
|
options.messages
|
|
476
478
|
);
|
|
479
|
+
const tools = this.transformTools(options.tools);
|
|
477
480
|
|
|
478
481
|
try {
|
|
479
482
|
await wait(2000);
|
|
@@ -483,7 +486,16 @@ export class GenericGeminiClient implements GenericClient {
|
|
|
483
486
|
config: {
|
|
484
487
|
systemInstruction,
|
|
485
488
|
thinkingConfig,
|
|
486
|
-
tools
|
|
489
|
+
tools,
|
|
490
|
+
...(tools.length && {
|
|
491
|
+
toolConfig: {
|
|
492
|
+
functionCallingConfig: {
|
|
493
|
+
mode: options.tool_choice === "none"
|
|
494
|
+
? FunctionCallingConfigMode.NONE
|
|
495
|
+
: FunctionCallingConfigMode.AUTO,
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
}),
|
|
487
499
|
maxOutputTokens: options.max_tokens,
|
|
488
500
|
},
|
|
489
501
|
});
|
package/src/clients/http.ts
CHANGED
|
@@ -247,9 +247,9 @@ export class HttpClient implements GenericClient {
|
|
|
247
247
|
max_tokens: options.max_tokens || 4000,
|
|
248
248
|
...this.extra_body,
|
|
249
249
|
|
|
250
|
-
...(options.tools && {
|
|
250
|
+
...(options.tools?.length && {
|
|
251
251
|
tools: options.tools,
|
|
252
|
-
tool_choice: "auto",
|
|
252
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
253
253
|
}),
|
|
254
254
|
};
|
|
255
255
|
|
|
@@ -297,9 +297,9 @@ export class HttpClient implements GenericClient {
|
|
|
297
297
|
...this.extra_body,
|
|
298
298
|
stream: true,
|
|
299
299
|
stream_options: { include_usage: true },
|
|
300
|
-
...(options.tools && {
|
|
300
|
+
...(options.tools?.length && {
|
|
301
301
|
tools: options.tools,
|
|
302
|
-
tool_choice: "auto",
|
|
302
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
303
303
|
}),
|
|
304
304
|
};
|
|
305
305
|
|
|
@@ -442,7 +442,10 @@ export class HttpClient implements GenericClient {
|
|
|
442
442
|
input,
|
|
443
443
|
...(instructions && { instructions }),
|
|
444
444
|
...(options.max_tokens && { max_output_tokens: options.max_tokens }),
|
|
445
|
-
...(tools?.length && {
|
|
445
|
+
...(tools?.length && {
|
|
446
|
+
tools,
|
|
447
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
448
|
+
}),
|
|
446
449
|
// Reasoning persistence: store server-side when requested, otherwise
|
|
447
450
|
// ask the provider to return encrypted reasoning content so we can
|
|
448
451
|
// thread the reasoning items back into the next request ourselves.
|
|
@@ -62,9 +62,9 @@ export class KnowhowMcpClient implements GenericClient {
|
|
|
62
62
|
...options,
|
|
63
63
|
max_tokens: options.max_tokens || 3000,
|
|
64
64
|
|
|
65
|
-
...(options.tools && {
|
|
65
|
+
...(options.tools?.length && {
|
|
66
66
|
tools: options.tools,
|
|
67
|
-
tool_choice: "auto",
|
|
67
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
68
68
|
}),
|
|
69
69
|
},
|
|
70
70
|
}
|
package/src/clients/openai.ts
CHANGED
|
@@ -235,9 +235,9 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
235
235
|
max_completion_tokens: Math.max(options.max_tokens ?? 0, 16_000),
|
|
236
236
|
reasoning_effort: this.resolveReasoningEffort(options),
|
|
237
237
|
}),
|
|
238
|
-
...(options.tools && {
|
|
238
|
+
...(options.tools?.length && {
|
|
239
239
|
tools: options.tools,
|
|
240
|
-
tool_choice: "auto",
|
|
240
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
241
241
|
}),
|
|
242
242
|
}, { signal: options.signal });
|
|
243
243
|
|
|
@@ -296,7 +296,10 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
296
296
|
max_tokens: options.max_tokens,
|
|
297
297
|
stream: true,
|
|
298
298
|
stream_options: { include_usage: true },
|
|
299
|
-
...(options.tools && {
|
|
299
|
+
...(options.tools?.length && {
|
|
300
|
+
tools: options.tools,
|
|
301
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
302
|
+
}),
|
|
300
303
|
}, { signal: options.signal });
|
|
301
304
|
|
|
302
305
|
let usage: CompletionResponse['usage'] | undefined;
|
|
@@ -501,7 +504,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
501
504
|
}),
|
|
502
505
|
...(tools?.length && {
|
|
503
506
|
tools,
|
|
504
|
-
tool_choice: "auto",
|
|
507
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
505
508
|
}),
|
|
506
509
|
} as any);
|
|
507
510
|
|
|
@@ -7,7 +7,8 @@ import { completions, embeddings, images, videos, audios, liveApi, ModelCatalogE
|
|
|
7
7
|
// ─── Model IDs ────────────────────────────────────────────────────────────────
|
|
8
8
|
|
|
9
9
|
export const GoogleModels = {
|
|
10
|
-
// Gemini 3.6 / 3.5
|
|
10
|
+
// Gemini 3.7 / 3.6 / 3.5
|
|
11
|
+
Gemini_37_Flash: "gemini-3.7-flash",
|
|
11
12
|
Gemini_36_Flash: "gemini-3.6-flash",
|
|
12
13
|
Gemini_35_Flash: "gemini-3.5-flash",
|
|
13
14
|
Gemini_35_Flash_Lite: "gemini-3.5-flash-lite",
|
|
@@ -86,6 +87,7 @@ export const GoogleEmbeddingModels: Record<string, string> = {
|
|
|
86
87
|
// ─── Modality arrays ──────────────────────────────────────────────────────────
|
|
87
88
|
|
|
88
89
|
export const GoogleTextModels: string[] = [
|
|
90
|
+
GoogleModels.Gemini_37_Flash,
|
|
89
91
|
GoogleModels.Gemini_36_Flash,
|
|
90
92
|
GoogleModels.Gemini_35_Flash,
|
|
91
93
|
GoogleModels.Gemini_35_Flash_Lite,
|
|
@@ -101,6 +103,7 @@ export const GoogleTextModels: string[] = [
|
|
|
101
103
|
|
|
102
104
|
// Models that support thinkingLevel (Gemini 3.x series)
|
|
103
105
|
export const GoogleThinkingLevelModels: string[] = [
|
|
106
|
+
GoogleModels.Gemini_37_Flash,
|
|
104
107
|
GoogleModels.Gemini_36_Flash,
|
|
105
108
|
GoogleModels.Gemini_35_Flash,
|
|
106
109
|
GoogleModels.Gemini_35_Flash_Lite,
|
|
@@ -214,7 +217,13 @@ export interface GeminiModelPricing extends ModelPricing {
|
|
|
214
217
|
}
|
|
215
218
|
|
|
216
219
|
export const GeminiPricing: Record<string, GeminiModelPricing> = {
|
|
217
|
-
// ── Gemini 3.6 / 3.5
|
|
220
|
+
// ── Gemini 3.7 / 3.6 / 3.5 ────────────────────────────────────────────────
|
|
221
|
+
// Introductory pricing through December 31, 2026. Standard pricing beginning
|
|
222
|
+
// January 1, 2027 is $1.50 input, $7.50 output, and $0.15 cached input.
|
|
223
|
+
[GoogleModels.Gemini_37_Flash]: {
|
|
224
|
+
input: 0.75, output: 3.75,
|
|
225
|
+
context_caching: 0.075, context_caching_storage: 0.5,
|
|
226
|
+
},
|
|
218
227
|
[GoogleModels.Gemini_36_Flash]: {
|
|
219
228
|
input: 1.5, output: 7.5,
|
|
220
229
|
context_caching: 0.15, context_caching_storage: 1.0,
|
|
@@ -7,6 +7,7 @@ import { completions, images, videos, ModelCatalogEntry, ModelPricing } from "./
|
|
|
7
7
|
// ─── Model IDs ────────────────────────────────────────────────────────────────
|
|
8
8
|
|
|
9
9
|
export const XaiModels = {
|
|
10
|
+
Grok4_6: "grok-4.6",
|
|
10
11
|
Grok_4_20_Reasoning: "grok-4.20-0309-reasoning",
|
|
11
12
|
Grok_4_20_NonReasoning: "grok-4.20-0309-non-reasoning",
|
|
12
13
|
Grok_4_20_MultiAgent: "grok-4.20-multi-agent-0309",
|
|
@@ -42,6 +43,7 @@ export const XaiModels = {
|
|
|
42
43
|
// ─── Modality arrays ──────────────────────────────────────────────────────────
|
|
43
44
|
|
|
44
45
|
export const XaiTextModels: string[] = [
|
|
46
|
+
XaiModels.Grok4_6,
|
|
45
47
|
XaiModels.Grok_4_20_Reasoning, XaiModels.Grok_4_20_NonReasoning,
|
|
46
48
|
XaiModels.Grok_4_20_MultiAgent,
|
|
47
49
|
XaiModels.Grok4_1_Fast_Reasoning, XaiModels.Grok4_1_Fast_NonReasoning,
|
|
@@ -61,8 +63,9 @@ export const XaiResponsesOnlyModels: string[] = [
|
|
|
61
63
|
];
|
|
62
64
|
|
|
63
65
|
// Models that support the reasoning_effort parameter
|
|
64
|
-
//
|
|
66
|
+
// Grok 4.6, Grok 4.20 multi-agent, and grok-3-mini variants support reasoning_effort
|
|
65
67
|
export const XaiReasoningModels: string[] = [
|
|
68
|
+
XaiModels.Grok4_6,
|
|
66
69
|
XaiModels.Grok_4_20_MultiAgent,
|
|
67
70
|
XaiModels.Grok3MiniBeta,
|
|
68
71
|
XaiModels.Grok3MiniFastBeta,
|
|
@@ -84,6 +87,7 @@ export const XaiVideoModels: string[] = [XaiModels.GrokImagineVideo];
|
|
|
84
87
|
// ─── Pricing (USD per 1M tokens / per-image / per-second) ────────────────────
|
|
85
88
|
|
|
86
89
|
export const XaiTextPricing: Record<string, ModelPricing> = {
|
|
90
|
+
[XaiModels.Grok4_6]: { input: 2.0, input_gt_200k: 4.0, cache_hit: 0.50, output: 6.0, output_gt_200k: 12.0, reasoningLevels: ["low", "medium", "high", "xhigh"] },
|
|
87
91
|
[XaiModels.Grok_4_20_Reasoning]: { input: 2.0, cache_hit: 0.20, output: 6.0, useResponsesApi: true },
|
|
88
92
|
[XaiModels.Grok_4_20_NonReasoning]: { input: 2.0, cache_hit: 0.20, output: 6.0, useResponsesApi: true },
|
|
89
93
|
[XaiModels.Grok_4_20_MultiAgent]: { input: 2.0, cache_hit: 0.20, output: 6.0, reasoningLevels: ["low", "medium", "high", "xhigh"], useResponsesApi: true },
|
package/src/clients/xai.ts
CHANGED
|
@@ -90,9 +90,9 @@ export class GenericXAIClient implements GenericClient {
|
|
|
90
90
|
// grok-3-mini models support reasoning_effort: "low" | "medium" | "high"
|
|
91
91
|
reasoning_effort: reasoningEffort,
|
|
92
92
|
}),
|
|
93
|
-
...(options.tools && {
|
|
93
|
+
...(options.tools?.length && {
|
|
94
94
|
tools: options.tools,
|
|
95
|
-
tool_choice: "auto",
|
|
95
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
96
96
|
}),
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -238,7 +238,10 @@ export class GenericXAIClient implements GenericClient {
|
|
|
238
238
|
...(instructions && { instructions }),
|
|
239
239
|
...(options.max_tokens && { max_output_tokens: Math.max(options.max_tokens, 16_000) }),
|
|
240
240
|
...(reasoningEffort && { reasoning: { effort: reasoningEffort } }),
|
|
241
|
-
...(tools?.length && {
|
|
241
|
+
...(tools?.length && {
|
|
242
|
+
tools,
|
|
243
|
+
tool_choice: options.tool_choice ?? "auto",
|
|
244
|
+
}),
|
|
242
245
|
// Reasoning persistence (same construct as OpenAI's Responses API):
|
|
243
246
|
// - store === true → xAI persists the response; next turn passes
|
|
244
247
|
// previous_response_id to continue the reasoning chain server-side.
|