@tyvm/knowhow 0.0.108 → 0.0.109-dev.2b94ba2
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/README.md +45 -0
- package/package.json +9 -4
- package/scripts/build-for-node.sh +10 -24
- package/scripts/publish.sh +86 -0
- package/src/agents/base/base.ts +10 -0
- package/src/agents/tools/execCommand.ts +49 -6
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +2 -4
- package/src/chat/CliChatService.ts +11 -2
- package/src/chat/modules/AgentModule.ts +61 -31
- package/src/chat/modules/SessionsModule.ts +47 -3
- package/src/chat/modules/SystemModule.ts +2 -2
- package/src/chat/renderer/CompactRenderer.ts +20 -0
- package/src/chat/renderer/ConsoleRenderer.ts +19 -0
- package/src/chat/renderer/FancyRenderer.ts +19 -0
- package/src/chat/renderer/types.ts +11 -0
- package/src/cli.ts +91 -659
- package/src/clients/anthropic.ts +18 -17
- package/src/clients/index.ts +31 -11
- package/src/clients/openai.ts +8 -5
- package/src/clients/types.ts +48 -10
- package/src/clients/withRetry.ts +89 -0
- package/src/cloudWorker.ts +175 -113
- package/src/commands/agent.ts +246 -0
- package/src/commands/misc.ts +174 -0
- package/src/commands/modules.ts +552 -0
- package/src/commands/services.ts +77 -0
- package/src/commands/workers.ts +168 -0
- package/src/config.ts +38 -1
- package/src/fileSync.ts +70 -29
- package/src/hashes.ts +35 -13
- package/src/index.ts +18 -0
- package/src/logger.ts +197 -0
- package/src/plugins/embedding.ts +11 -6
- package/src/plugins/plugins.ts +0 -21
- package/src/plugins/vim.ts +5 -16
- package/src/processors/JsonCompressor.ts +6 -6
- package/src/services/EventService.ts +61 -1
- package/src/services/KnowhowClient.ts +34 -4
- package/src/services/MediaProcessorService.ts +79 -10
- package/src/services/modules/index.ts +102 -53
- package/src/services/modules/types.ts +6 -0
- package/src/tunnel.ts +216 -0
- package/src/types.ts +0 -1
- package/src/worker.ts +105 -312
- package/src/workers/auth/WsMiddleware.ts +99 -0
- package/src/workers/auth/authMiddleware.ts +104 -0
- package/src/workers/auth/types.ts +14 -2
- package/src/workers/tools/index.ts +2 -0
- package/src/workers/tools/reloadConfig.ts +84 -0
- package/tests/services/WorkerReloadConfig.test.ts +141 -0
- package/tests/unit/clients/AIClient.test.ts +446 -0
- package/tests/unit/clients/withRetry.test.ts +319 -0
- package/tests/unit/commands/github-credentials.test.ts +210 -0
- package/tests/unit/modules/moduleLoading.test.ts +39 -37
- package/tests/unit/plugins/pluginLoading.test.ts +0 -85
- package/ts_build/package.json +9 -4
- package/ts_build/src/agents/base/base.js +11 -0
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
- package/ts_build/src/agents/tools/execCommand.js +39 -5
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -4
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/chat/CliChatService.js +14 -2
- package/ts_build/src/chat/CliChatService.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.d.ts +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +43 -20
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/SessionsModule.js +37 -3
- package/ts_build/src/chat/modules/SessionsModule.js.map +1 -1
- package/ts_build/src/chat/modules/SystemModule.js +2 -2
- package/ts_build/src/chat/modules/SystemModule.js.map +1 -1
- package/ts_build/src/chat/renderer/CompactRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js +16 -0
- package/ts_build/src/chat/renderer/CompactRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/ConsoleRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js +16 -0
- package/ts_build/src/chat/renderer/ConsoleRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/FancyRenderer.d.ts +4 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js +16 -0
- package/ts_build/src/chat/renderer/FancyRenderer.js.map +1 -1
- package/ts_build/src/chat/renderer/types.d.ts +2 -0
- package/ts_build/src/cli.js +47 -519
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +5 -5
- package/ts_build/src/clients/anthropic.js +18 -17
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/index.js +9 -10
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.js +4 -4
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +15 -8
- package/ts_build/src/clients/withRetry.d.ts +2 -0
- package/ts_build/src/clients/withRetry.js +60 -0
- package/ts_build/src/clients/withRetry.js.map +1 -0
- package/ts_build/src/cloudWorker.d.ts +14 -0
- package/ts_build/src/cloudWorker.js +105 -66
- package/ts_build/src/cloudWorker.js.map +1 -1
- package/ts_build/src/commands/agent.d.ts +6 -0
- package/ts_build/src/commands/agent.js +229 -0
- package/ts_build/src/commands/agent.js.map +1 -0
- package/ts_build/src/commands/misc.d.ts +10 -0
- package/ts_build/src/commands/misc.js +197 -0
- package/ts_build/src/commands/misc.js.map +1 -0
- package/ts_build/src/commands/modules.d.ts +3 -0
- package/ts_build/src/commands/modules.js +487 -0
- package/ts_build/src/commands/modules.js.map +1 -0
- package/ts_build/src/commands/services.d.ts +5 -0
- package/ts_build/src/commands/services.js +87 -0
- package/ts_build/src/commands/services.js.map +1 -0
- package/ts_build/src/commands/workers.d.ts +6 -0
- package/ts_build/src/commands/workers.js +168 -0
- package/ts_build/src/commands/workers.js.map +1 -0
- package/ts_build/src/config.d.ts +1 -0
- package/ts_build/src/config.js +33 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/fileSync.d.ts +6 -0
- package/ts_build/src/fileSync.js +50 -23
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/hashes.d.ts +2 -2
- package/ts_build/src/hashes.js +35 -9
- package/ts_build/src/hashes.js.map +1 -1
- package/ts_build/src/index.d.ts +1 -0
- package/ts_build/src/index.js +17 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/logger.d.ts +21 -0
- package/ts_build/src/logger.js +106 -0
- package/ts_build/src/logger.js.map +1 -0
- package/ts_build/src/plugins/embedding.js +4 -3
- package/ts_build/src/plugins/embedding.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +0 -2
- package/ts_build/src/plugins/plugins.js +0 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/vim.js +3 -9
- package/ts_build/src/plugins/vim.js.map +1 -1
- package/ts_build/src/processors/JsonCompressor.js +4 -4
- package/ts_build/src/processors/JsonCompressor.js.map +1 -1
- package/ts_build/src/services/EventService.d.ts +6 -1
- package/ts_build/src/services/EventService.js +29 -0
- package/ts_build/src/services/EventService.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +13 -1
- package/ts_build/src/services/KnowhowClient.js +19 -2
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/MediaProcessorService.d.ts +5 -4
- package/ts_build/src/services/MediaProcessorService.js +53 -8
- package/ts_build/src/services/MediaProcessorService.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +33 -0
- package/ts_build/src/services/modules/index.js +73 -49
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/services/modules/types.d.ts +6 -0
- package/ts_build/src/tunnel.d.ts +27 -0
- package/ts_build/src/tunnel.js +112 -0
- package/ts_build/src/tunnel.js.map +1 -0
- package/ts_build/src/types.d.ts +0 -1
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +1 -4
- package/ts_build/src/worker.js +59 -227
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/src/workers/auth/WsMiddleware.d.ts +8 -0
- package/ts_build/src/workers/auth/WsMiddleware.js +65 -0
- package/ts_build/src/workers/auth/WsMiddleware.js.map +1 -0
- package/ts_build/src/workers/auth/authMiddleware.d.ts +3 -0
- package/ts_build/src/workers/auth/authMiddleware.js +60 -0
- package/ts_build/src/workers/auth/authMiddleware.js.map +1 -0
- package/ts_build/src/workers/auth/types.d.ts +8 -1
- package/ts_build/src/workers/tools/index.d.ts +2 -0
- package/ts_build/src/workers/tools/index.js +4 -1
- package/ts_build/src/workers/tools/index.js.map +1 -1
- package/ts_build/src/workers/tools/reloadConfig.d.ts +14 -0
- package/ts_build/src/workers/tools/reloadConfig.js +48 -0
- package/ts_build/src/workers/tools/reloadConfig.js.map +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.d.ts +1 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js +86 -0
- package/ts_build/tests/services/WorkerReloadConfig.test.js.map +1 -0
- package/ts_build/tests/unit/clients/AIClient.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/AIClient.test.js +339 -0
- package/ts_build/tests/unit/clients/AIClient.test.js.map +1 -0
- package/ts_build/tests/unit/clients/withRetry.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/withRetry.test.js +225 -0
- package/ts_build/tests/unit/clients/withRetry.test.js.map +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js +145 -0
- package/ts_build/tests/unit/commands/github-credentials.test.js.map +1 -0
- package/ts_build/tests/unit/modules/moduleLoading.test.js +20 -26
- package/ts_build/tests/unit/modules/moduleLoading.test.js.map +1 -1
- package/ts_build/tests/unit/plugins/pluginLoading.test.js +0 -65
- package/ts_build/tests/unit/plugins/pluginLoading.test.js.map +1 -1
- package/src/agents/tools/executeScript/README.md +0 -94
- package/src/agents/tools/executeScript/definition.ts +0 -79
- package/src/agents/tools/executeScript/examples/dependency-injection-validation.ts +0 -272
- package/src/agents/tools/executeScript/examples/quick-test.ts +0 -74
- package/src/agents/tools/executeScript/examples/serialization-test.ts +0 -321
- package/src/agents/tools/executeScript/examples/test-runner.ts +0 -197
- package/src/agents/tools/executeScript/index.ts +0 -98
- package/src/services/script-execution/SandboxContext.ts +0 -282
- package/src/services/script-execution/ScriptExecutor.ts +0 -441
- package/src/services/script-execution/ScriptPolicy.ts +0 -194
- package/src/services/script-execution/ScriptTracer.ts +0 -249
- package/src/services/script-execution/types.ts +0 -134
- package/ts_build/src/agents/tools/executeScript/definition.d.ts +0 -2
- package/ts_build/src/agents/tools/executeScript/definition.js +0 -76
- package/ts_build/src/agents/tools/executeScript/definition.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.d.ts +0 -18
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js +0 -192
- package/ts_build/src/agents/tools/executeScript/examples/dependency-injection-validation.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.d.ts +0 -3
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js +0 -64
- package/ts_build/src/agents/tools/executeScript/examples/quick-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.d.ts +0 -15
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js +0 -266
- package/ts_build/src/agents/tools/executeScript/examples/serialization-test.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.d.ts +0 -4
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js +0 -208
- package/ts_build/src/agents/tools/executeScript/examples/test-runner.js.map +0 -1
- package/ts_build/src/agents/tools/executeScript/index.d.ts +0 -28
- package/ts_build/src/agents/tools/executeScript/index.js +0 -72
- package/ts_build/src/agents/tools/executeScript/index.js.map +0 -1
- package/ts_build/src/services/script-execution/SandboxContext.d.ts +0 -34
- package/ts_build/src/services/script-execution/SandboxContext.js +0 -189
- package/ts_build/src/services/script-execution/SandboxContext.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptExecutor.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptExecutor.js +0 -269
- package/ts_build/src/services/script-execution/ScriptExecutor.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptPolicy.d.ts +0 -28
- package/ts_build/src/services/script-execution/ScriptPolicy.js +0 -115
- package/ts_build/src/services/script-execution/ScriptPolicy.js.map +0 -1
- package/ts_build/src/services/script-execution/ScriptTracer.d.ts +0 -19
- package/ts_build/src/services/script-execution/ScriptTracer.js +0 -186
- package/ts_build/src/services/script-execution/ScriptTracer.js.map +0 -1
- package/ts_build/src/services/script-execution/types.d.ts +0 -108
- package/ts_build/src/services/script-execution/types.js +0 -3
- package/ts_build/src/services/script-execution/types.js.map +0 -1
package/src/clients/anthropic.ts
CHANGED
|
@@ -40,11 +40,11 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
handleToolCaching(tools: Anthropic.Tool[]) {
|
|
43
|
+
handleToolCaching(tools: Anthropic.Tool[], longTtl = false) {
|
|
44
44
|
const lastTool = tools[tools.length - 1];
|
|
45
45
|
|
|
46
46
|
if (lastTool) {
|
|
47
|
-
lastTool.cache_control = { type: "ephemeral" };
|
|
47
|
+
lastTool.cache_control = longTtl ? { type: "ephemeral", ttl: "1h" } as any : { type: "ephemeral" };
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -94,7 +94,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
94
94
|
return cleaned;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
transformTools(tools?: Tool[]): Anthropic.Tool[] {
|
|
97
|
+
transformTools(tools?: Tool[], longTtl = false): Anthropic.Tool[] {
|
|
98
98
|
if (!tools) {
|
|
99
99
|
return [];
|
|
100
100
|
}
|
|
@@ -104,7 +104,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
104
104
|
input_schema: this.cleanSchemaForAnthropic(tool.function.parameters) as any,
|
|
105
105
|
}));
|
|
106
106
|
|
|
107
|
-
this.handleToolCaching(transformed);
|
|
107
|
+
this.handleToolCaching(transformed, longTtl);
|
|
108
108
|
|
|
109
109
|
return transformed;
|
|
110
110
|
}
|
|
@@ -153,16 +153,16 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
153
153
|
return messages;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
cacheLastContent(message: MessageParam) {
|
|
156
|
+
cacheLastContent(message: MessageParam, longTtl = false) {
|
|
157
157
|
if (Array.isArray(message.content)) {
|
|
158
158
|
const lastMessage = message.content[message.content.length - 1];
|
|
159
159
|
if (
|
|
160
160
|
lastMessage.type !== "thinking" &&
|
|
161
161
|
lastMessage.type !== "redacted_thinking"
|
|
162
162
|
) {
|
|
163
|
-
lastMessage.cache_control =
|
|
164
|
-
type: "ephemeral",
|
|
165
|
-
|
|
163
|
+
lastMessage.cache_control = longTtl
|
|
164
|
+
? ({ type: "ephemeral", ttl: "1h" } as any)
|
|
165
|
+
: { type: "ephemeral" };
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -179,7 +179,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
handleMessageCaching(groupedMessages: MessageParam[]) {
|
|
182
|
+
handleMessageCaching(groupedMessages: MessageParam[], longTtl = false) {
|
|
183
183
|
this.handleClearingCache(groupedMessages);
|
|
184
184
|
|
|
185
185
|
// find the last two messages and mark them as ephemeral
|
|
@@ -189,7 +189,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
189
189
|
|
|
190
190
|
for (const m of lastTwoUserMessages) {
|
|
191
191
|
if (Array.isArray(m.content)) {
|
|
192
|
-
this.cacheLastContent(m);
|
|
192
|
+
this.cacheLastContent(m, longTtl);
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -203,7 +203,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
203
203
|
}
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
transformMessages(messages: Message[]): MessageParam[] {
|
|
206
|
+
transformMessages(messages: Message[], longTtl = false): MessageParam[] {
|
|
207
207
|
const toolCalls = messages.flatMap((msg) => msg.tool_calls || []);
|
|
208
208
|
const claudeMessages: MessageParam[] = messages
|
|
209
209
|
.filter((msg) => msg.role !== "system")
|
|
@@ -302,7 +302,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
302
302
|
|
|
303
303
|
const groupedMessages = this.combineMessages(claudeMessages);
|
|
304
304
|
|
|
305
|
-
this.handleMessageCaching(groupedMessages);
|
|
305
|
+
this.handleMessageCaching(groupedMessages, longTtl);
|
|
306
306
|
|
|
307
307
|
return groupedMessages;
|
|
308
308
|
}
|
|
@@ -349,14 +349,15 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
349
349
|
async createChatCompletion(
|
|
350
350
|
options: CompletionOptions
|
|
351
351
|
): Promise<CompletionResponse> {
|
|
352
|
+
const longTtl = !!options.long_ttl_cache;
|
|
352
353
|
const systemMessage = options.messages
|
|
353
354
|
.filter((msg) => msg.role === "system")
|
|
354
355
|
.map((msg) => msg.content || "")
|
|
355
356
|
.join("\n");
|
|
356
357
|
|
|
357
|
-
const claudeMessages = this.transformMessages(options.messages);
|
|
358
|
+
const claudeMessages = this.transformMessages(options.messages, longTtl);
|
|
358
359
|
|
|
359
|
-
const tools = this.transformTools(options.tools);
|
|
360
|
+
const tools = this.transformTools(options.tools, longTtl);
|
|
360
361
|
try {
|
|
361
362
|
const response = await this.client.messages.create({
|
|
362
363
|
model: options.model,
|
|
@@ -365,7 +366,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
365
366
|
? [
|
|
366
367
|
{
|
|
367
368
|
text: systemMessage,
|
|
368
|
-
cache_control: { type: "ephemeral" },
|
|
369
|
+
cache_control: longTtl ? ({ type: "ephemeral", ttl: "1h" } as any) : { type: "ephemeral" },
|
|
369
370
|
type: "text",
|
|
370
371
|
},
|
|
371
372
|
]
|
|
@@ -375,7 +376,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
375
376
|
tool_choice: { type: "auto" },
|
|
376
377
|
tools,
|
|
377
378
|
}),
|
|
378
|
-
});
|
|
379
|
+
}, { signal: options.signal });
|
|
379
380
|
|
|
380
381
|
if (!response.content || !response.content.length) {
|
|
381
382
|
console.log("no content in Anthropic response", response);
|
|
@@ -424,7 +425,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
424
425
|
usd_cost: this.calculateCost(options.model, response.usage),
|
|
425
426
|
};
|
|
426
427
|
} catch (err) {
|
|
427
|
-
if ("headers" in err && err.headers["x-should-retry"] === "true") {
|
|
428
|
+
if ("headers" in err && err.headers?.["x-should-retry"] === "true") {
|
|
428
429
|
console.warn("Retrying failed request", err);
|
|
429
430
|
await wait(2500);
|
|
430
431
|
return this.createChatCompletion(options);
|
package/src/clients/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { ContextLimits } from "./contextLimits";
|
|
|
33
33
|
import { OpenAiTextPricing } from "./pricing/openai";
|
|
34
34
|
import { AnthropicTextPricing } from "./pricing/anthropic";
|
|
35
35
|
import { GeminiPricing } from "./pricing/google";
|
|
36
|
+
import { withRetry } from "./withRetry";
|
|
36
37
|
import {
|
|
37
38
|
XaiTextPricing,
|
|
38
39
|
XaiImagePricing,
|
|
@@ -577,11 +578,12 @@ export class AIClient {
|
|
|
577
578
|
* @param modelQuery - the model name to search for (can be partial/normalized)
|
|
578
579
|
* @param provider - optional provider to restrict search to
|
|
579
580
|
*/
|
|
580
|
-
findModelFuzzy(
|
|
581
|
+
findModelFuzzy(
|
|
582
|
+
modelQuery: string,
|
|
583
|
+
provider?: string
|
|
584
|
+
): { provider: string; model: string } | undefined {
|
|
581
585
|
const queryNorm = AIClient.normalizeModelId(modelQuery);
|
|
582
|
-
const providers = provider
|
|
583
|
-
? [provider]
|
|
584
|
-
: Object.keys(this.clientModels);
|
|
586
|
+
const providers = provider ? [provider] : Object.keys(this.clientModels);
|
|
585
587
|
|
|
586
588
|
for (const p of providers) {
|
|
587
589
|
const models = (this.clientModels[p] as string[]) ?? [];
|
|
@@ -664,7 +666,10 @@ export class AIClient {
|
|
|
664
666
|
} model registered. Try using ${JSON.stringify(this.listAllModels())}`
|
|
665
667
|
);
|
|
666
668
|
}
|
|
667
|
-
return
|
|
669
|
+
return withRetry(
|
|
670
|
+
(signal) => client.createChatCompletion({ ...options, model, signal }),
|
|
671
|
+
options
|
|
672
|
+
);
|
|
668
673
|
}
|
|
669
674
|
|
|
670
675
|
async createEmbedding(
|
|
@@ -679,7 +684,10 @@ export class AIClient {
|
|
|
679
684
|
} model registered. Try using ${JSON.stringify(this.listAllModels())}`
|
|
680
685
|
);
|
|
681
686
|
}
|
|
682
|
-
return
|
|
687
|
+
return withRetry(
|
|
688
|
+
(signal) => client.createEmbedding({ ...options, model, signal }),
|
|
689
|
+
options
|
|
690
|
+
);
|
|
683
691
|
}
|
|
684
692
|
|
|
685
693
|
async createAudioTranscription(
|
|
@@ -692,7 +700,10 @@ export class AIClient {
|
|
|
692
700
|
`Provider ${provider} does not support audio transcription.`
|
|
693
701
|
);
|
|
694
702
|
}
|
|
695
|
-
return
|
|
703
|
+
return withRetry(
|
|
704
|
+
(signal) => client.createAudioTranscription({ ...options, signal }),
|
|
705
|
+
options
|
|
706
|
+
);
|
|
696
707
|
}
|
|
697
708
|
|
|
698
709
|
async createAudioGeneration(
|
|
@@ -710,7 +721,10 @@ export class AIClient {
|
|
|
710
721
|
`Model ${options.model} not registered for provider ${provider}.`
|
|
711
722
|
);
|
|
712
723
|
}
|
|
713
|
-
return
|
|
724
|
+
return withRetry(
|
|
725
|
+
(signal) => client.createAudioGeneration({ ...options, model, signal }),
|
|
726
|
+
options
|
|
727
|
+
);
|
|
714
728
|
}
|
|
715
729
|
|
|
716
730
|
async createImageGeneration(
|
|
@@ -728,7 +742,10 @@ export class AIClient {
|
|
|
728
742
|
`Model ${options.model} not registered for provider ${provider}.`
|
|
729
743
|
);
|
|
730
744
|
}
|
|
731
|
-
return
|
|
745
|
+
return withRetry(
|
|
746
|
+
(signal) => client.createImageGeneration({ ...options, model, signal }),
|
|
747
|
+
options
|
|
748
|
+
);
|
|
732
749
|
}
|
|
733
750
|
|
|
734
751
|
async createVideoGeneration(
|
|
@@ -746,7 +763,10 @@ export class AIClient {
|
|
|
746
763
|
`Model ${options.model} not registered for provider ${provider}.`
|
|
747
764
|
);
|
|
748
765
|
}
|
|
749
|
-
return
|
|
766
|
+
return withRetry(
|
|
767
|
+
(signal) => client.createVideoGeneration({ ...options, model, signal }),
|
|
768
|
+
options
|
|
769
|
+
);
|
|
750
770
|
}
|
|
751
771
|
|
|
752
772
|
async getVideoStatus(
|
|
@@ -835,7 +855,7 @@ export class AIClient {
|
|
|
835
855
|
const splitModel = m.id.split("/");
|
|
836
856
|
|
|
837
857
|
if (splitModel.length < 2) {
|
|
838
|
-
console.
|
|
858
|
+
console.warn(`Cannot parse model format: ${m.id}`);
|
|
839
859
|
}
|
|
840
860
|
|
|
841
861
|
const provider = splitModel.length > 1 ? splitModel[0] : "";
|
package/src/clients/openai.ts
CHANGED
|
@@ -63,6 +63,10 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Execute a function with timeout, retries, and exponential backoff.
|
|
68
|
+
* Retriable errors: 5xx, timeout, ECONNRESET, ETIMEDOUT, rate limits (429).
|
|
69
|
+
*/
|
|
66
70
|
reasoningEffort(
|
|
67
71
|
messages: CompletionOptions["messages"]
|
|
68
72
|
): "low" | "medium" | "high" {
|
|
@@ -155,12 +159,11 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
155
159
|
max_completion_tokens: Math.max(options.max_tokens ?? 0, 16_000),
|
|
156
160
|
reasoning_effort: this.resolveReasoningEffort(options),
|
|
157
161
|
}),
|
|
158
|
-
|
|
159
162
|
...(options.tools && {
|
|
160
163
|
tools: options.tools,
|
|
161
164
|
tool_choice: "auto",
|
|
162
165
|
}),
|
|
163
|
-
});
|
|
166
|
+
}, { signal: options.signal });
|
|
164
167
|
|
|
165
168
|
const usdCost = this.calculateCost(options.model, response.usage);
|
|
166
169
|
|
|
@@ -453,7 +456,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
453
456
|
prompt: options.prompt,
|
|
454
457
|
response_format: options.response_format || "verbose_json",
|
|
455
458
|
temperature: options.temperature,
|
|
456
|
-
});
|
|
459
|
+
}, { signal: options.signal });
|
|
457
460
|
|
|
458
461
|
// Calculate cost: $0.006 per minute for Whisper
|
|
459
462
|
const duration = typeof response === "object" && "duration" in response && typeof response.duration === "number"
|
|
@@ -489,7 +492,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
489
492
|
voice: options.voice as any,
|
|
490
493
|
response_format: options.response_format || "mp3",
|
|
491
494
|
speed: options.speed,
|
|
492
|
-
});
|
|
495
|
+
}, { signal: options.signal });
|
|
493
496
|
|
|
494
497
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
495
498
|
|
|
@@ -518,7 +521,7 @@ export class GenericOpenAiClient implements GenericClient {
|
|
|
518
521
|
style: options.style,
|
|
519
522
|
response_format: options.response_format,
|
|
520
523
|
user: options.user,
|
|
521
|
-
});
|
|
524
|
+
}, { signal: options.signal });
|
|
522
525
|
|
|
523
526
|
// Cost calculation varies by model and settings
|
|
524
527
|
// DALL-E 3: $0.040-$0.120 per image depending on quality/size
|
package/src/clients/types.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
export type ModelModality =
|
|
1
|
+
export type ModelModality =
|
|
2
|
+
| "completion"
|
|
3
|
+
| "embedding"
|
|
4
|
+
| "image"
|
|
5
|
+
| "audio"
|
|
6
|
+
| "video"
|
|
7
|
+
| "transcription";
|
|
2
8
|
|
|
3
9
|
export type MessageContent =
|
|
4
10
|
| { type: "text"; text: string }
|
|
@@ -8,7 +14,7 @@ export type MessageContent =
|
|
|
8
14
|
|
|
9
15
|
export interface Message {
|
|
10
16
|
role: "system" | "user" | "assistant" | "tool";
|
|
11
|
-
content?: string | MessageContent[];
|
|
17
|
+
content?: string | MessageContent[] | null;
|
|
12
18
|
|
|
13
19
|
name?: string;
|
|
14
20
|
tool_call_id?: string;
|
|
@@ -16,7 +22,7 @@ export interface Message {
|
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
export interface OutputMessage extends Message {
|
|
19
|
-
content
|
|
25
|
+
content?: string | null;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export interface ToolProp {
|
|
@@ -51,7 +57,30 @@ export interface ToolCall {
|
|
|
51
57
|
};
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
export interface
|
|
60
|
+
export interface RetryOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Request timeout in milliseconds per attempt. If the request does not complete
|
|
63
|
+
* within this time it is aborted and retried according to maxRetries.
|
|
64
|
+
*/
|
|
65
|
+
timeout?: number;
|
|
66
|
+
/**
|
|
67
|
+
* Maximum number of retry attempts for retriable errors (5xx, timeout, ECONNRESET, 429).
|
|
68
|
+
* Default: 2. Set to 0 to disable retries.
|
|
69
|
+
*/
|
|
70
|
+
maxRetries?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Base backoff delay in milliseconds for exponential retry backoff.
|
|
73
|
+
* Default: 1000ms. Each retry waits backoffMs * 2^attempt ms.
|
|
74
|
+
*/
|
|
75
|
+
backoffMs?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Optional external AbortSignal. When the signal is aborted the current
|
|
78
|
+
* attempt is cancelled immediately and no further retries are made.
|
|
79
|
+
*/
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface CompletionOptions extends RetryOptions {
|
|
55
84
|
model: string;
|
|
56
85
|
messages: Message[];
|
|
57
86
|
tools?: Tool[];
|
|
@@ -61,6 +90,13 @@ export interface CompletionOptions {
|
|
|
61
90
|
* Maps to: OpenAI reasoning_effort, xAI reasoning.effort, Gemini thinkingLevel/thinkingBudget, Anthropic thinking budget.
|
|
62
91
|
* "low" = minimal thinking, "medium" = balanced, "high" = maximum reasoning */
|
|
63
92
|
reasoning_effort?: "low" | "medium" | "high";
|
|
93
|
+
/**
|
|
94
|
+
* When true, hints to the client that this task is long-running and it should
|
|
95
|
+
* use a long-TTL cache where available.
|
|
96
|
+
* - Anthropic: enables the `extended-cache-ttl-2025-02-19` beta and sets
|
|
97
|
+
* `cache_control.ttl` to 3600 (1 hour) instead of the default 5-minute ephemeral cache.
|
|
98
|
+
*/
|
|
99
|
+
long_ttl_cache?: boolean;
|
|
64
100
|
}
|
|
65
101
|
|
|
66
102
|
/**
|
|
@@ -100,7 +136,7 @@ export interface CompletionResponse {
|
|
|
100
136
|
usd_cost?: number;
|
|
101
137
|
}
|
|
102
138
|
|
|
103
|
-
export interface EmbeddingOptions {
|
|
139
|
+
export interface EmbeddingOptions extends RetryOptions {
|
|
104
140
|
input: string;
|
|
105
141
|
model?: string;
|
|
106
142
|
}
|
|
@@ -119,7 +155,7 @@ export interface EmbeddingResponse {
|
|
|
119
155
|
usd_cost?: number;
|
|
120
156
|
}
|
|
121
157
|
|
|
122
|
-
export interface AudioTranscriptionOptions {
|
|
158
|
+
export interface AudioTranscriptionOptions extends RetryOptions {
|
|
123
159
|
file: Blob | File | any; // Support for Node.js ReadStream or web File/Blob
|
|
124
160
|
model?: string;
|
|
125
161
|
language?: string;
|
|
@@ -149,7 +185,7 @@ export interface AudioTranscriptionResponse {
|
|
|
149
185
|
usd_cost?: number;
|
|
150
186
|
}
|
|
151
187
|
|
|
152
|
-
export interface AudioGenerationOptions {
|
|
188
|
+
export interface AudioGenerationOptions extends RetryOptions {
|
|
153
189
|
model: string;
|
|
154
190
|
input: string;
|
|
155
191
|
voice: string; // e.g. "alloy", "echo", "fable", "onyx", "nova", "shimmer" for OpenAI; "Kore", "Puck" etc. for Gemini
|
|
@@ -163,7 +199,7 @@ export interface AudioGenerationResponse {
|
|
|
163
199
|
usd_cost?: number;
|
|
164
200
|
}
|
|
165
201
|
|
|
166
|
-
export interface ImageGenerationOptions {
|
|
202
|
+
export interface ImageGenerationOptions extends RetryOptions {
|
|
167
203
|
model: string;
|
|
168
204
|
prompt: string;
|
|
169
205
|
n?: number;
|
|
@@ -184,7 +220,7 @@ export interface ImageGenerationResponse {
|
|
|
184
220
|
usd_cost?: number;
|
|
185
221
|
}
|
|
186
222
|
|
|
187
|
-
export interface VideoGenerationOptions {
|
|
223
|
+
export interface VideoGenerationOptions extends RetryOptions {
|
|
188
224
|
model: string;
|
|
189
225
|
prompt: string;
|
|
190
226
|
duration?: number; // seconds
|
|
@@ -294,7 +330,9 @@ export interface GenericClient {
|
|
|
294
330
|
* When modality is provided, return only models for that modality (static list).
|
|
295
331
|
* When omitted, return ALL models (backward compat — may do a live API call).
|
|
296
332
|
*/
|
|
297
|
-
getModels(
|
|
333
|
+
getModels(
|
|
334
|
+
modality?: ModelModality
|
|
335
|
+
): Promise<{ id: string; modality?: ModelModality[] }[]>;
|
|
298
336
|
/**
|
|
299
337
|
* Returns the context window limit and compression threshold for a given model,
|
|
300
338
|
* or undefined if the model is not known to this client.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared retry/timeout helper for all AI clients.
|
|
3
|
+
*
|
|
4
|
+
* Executes `fn` with exponential backoff for retriable errors:
|
|
5
|
+
* - Rate limits (429)
|
|
6
|
+
* - Timeouts (AbortError, ETIMEDOUT, ECONNRESET)
|
|
7
|
+
* - Server errors (5xx)
|
|
8
|
+
*
|
|
9
|
+
* @param fn Function to execute. Receives a combined AbortSignal
|
|
10
|
+
* that fires on per-attempt timeout OR external signal abort.
|
|
11
|
+
* @param opts Any object with optional RetryOptions fields (timeout, maxRetries,
|
|
12
|
+
* backoffMs, signal). Extra fields are ignored — so you can pass the
|
|
13
|
+
* full options object from any AI method directly.
|
|
14
|
+
* - timeout: Per-attempt timeout in ms. No timeout if omitted.
|
|
15
|
+
* - maxRetries: Max retry attempts after first failure. Default: 2.
|
|
16
|
+
* - backoffMs: Base backoff delay in ms. Default: 1000.
|
|
17
|
+
* - signal: Optional external AbortSignal. When aborted, the current
|
|
18
|
+
* attempt is cancelled and no further retries are made.
|
|
19
|
+
*/
|
|
20
|
+
import type { RetryOptions } from "./types";
|
|
21
|
+
|
|
22
|
+
export async function withRetry<T>(
|
|
23
|
+
fn: (signal?: AbortSignal) => Promise<T>,
|
|
24
|
+
opts: RetryOptions = {}
|
|
25
|
+
): Promise<T> {
|
|
26
|
+
const maxRetries = opts.maxRetries ?? 2;
|
|
27
|
+
const backoffMs = opts.backoffMs ?? 1000;
|
|
28
|
+
const timeout = opts.timeout;
|
|
29
|
+
const externalSignal = opts.signal;
|
|
30
|
+
|
|
31
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
32
|
+
// If the external signal is already aborted, bail out immediately.
|
|
33
|
+
if (externalSignal?.aborted) {
|
|
34
|
+
throw externalSignal.reason ?? new DOMException("Aborted", "AbortError");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
38
|
+
// Combine per-attempt timeout with the external signal into one controller.
|
|
39
|
+
const controller = timeout || externalSignal ? new AbortController() : undefined;
|
|
40
|
+
|
|
41
|
+
if (controller) {
|
|
42
|
+
if (timeout) {
|
|
43
|
+
timer = setTimeout(() => controller.abort(new DOMException("Request timed out", "TimeoutError")), timeout);
|
|
44
|
+
}
|
|
45
|
+
// Forward external signal abort into our combined controller.
|
|
46
|
+
if (externalSignal) {
|
|
47
|
+
const onExternalAbort = () => controller.abort(externalSignal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
48
|
+
if (externalSignal.aborted) {
|
|
49
|
+
controller.abort(externalSignal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
50
|
+
} else {
|
|
51
|
+
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
52
|
+
// Clean up the listener after the attempt resolves/rejects.
|
|
53
|
+
controller.signal.addEventListener("abort", () =>
|
|
54
|
+
externalSignal.removeEventListener("abort", onExternalAbort), { once: true }
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await fn(controller?.signal);
|
|
62
|
+
return result;
|
|
63
|
+
} catch (err: unknown) {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
// If the external signal was aborted, don't retry — propagate immediately.
|
|
66
|
+
if (externalSignal?.aborted) {
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
const errStr = String(err);
|
|
70
|
+
const isRetriable =
|
|
71
|
+
errStr.includes('429') ||
|
|
72
|
+
errStr.includes('timeout') ||
|
|
73
|
+
errStr.includes('TimeoutError') ||
|
|
74
|
+
errStr.includes('ECONNRESET') ||
|
|
75
|
+
errStr.includes('ETIMEDOUT') ||
|
|
76
|
+
errStr.includes('AbortError') ||
|
|
77
|
+
/5\d\d/.test(errStr);
|
|
78
|
+
if (isRetriable && attempt < maxRetries) {
|
|
79
|
+
const delay = backoffMs * Math.pow(2, attempt);
|
|
80
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
} finally {
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw new Error('withRetry: exhausted retries');
|
|
89
|
+
}
|