@tyvm/knowhow 0.0.155 → 0.0.157
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 +15 -5
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -0
- package/src/agents/tools/loadImageAsBase64.ts +83 -0
- package/src/auth/keyAuth.ts +5 -5
- package/src/chat/modules/AgentModule.ts +3 -3
- package/src/chat/modules/RemoteSyncModule.ts +45 -17
- package/src/clients/anthropic.ts +156 -79
- package/src/clients/attribution.ts +53 -0
- package/src/clients/http.ts +211 -84
- package/src/clients/index.ts +21 -9
- package/src/clients/openai.ts +441 -220
- package/src/clients/openrouter.ts +10 -3
- package/src/clients/pricing/openai.ts +15 -1
- package/src/clients/pricing/types.ts +15 -0
- package/src/clients/responseMetadata.ts +221 -0
- package/src/clients/types.ts +49 -4
- package/src/commands/services.ts +23 -18
- package/src/login.ts +22 -6
- package/src/processors/Base64ImageDetector.ts +12 -11
- package/src/tunnel.ts +13 -3
- package/tests/processors/Base64ImageDetector.test.ts +32 -0
- package/tests/unit/agents/tools/loadImageAsBase64.test.ts +44 -0
- package/tests/unit/auth/keyAuth.test.ts +21 -0
- package/tests/unit/clients/anthropic.responseMetadata.test.ts +81 -0
- package/tests/unit/clients/attribution.test.ts +185 -0
- package/tests/unit/clients/http.responseMetadata.test.ts +86 -0
- package/tests/unit/clients/openai.responseMetadata.test.ts +90 -0
- package/tests/unit/clients/responseMetadata.test.ts +123 -0
- package/tests/unit/login.test.ts +39 -1
- package/tests/unit/services/Mcp.test.ts +56 -0
- package/tests/unit/tunnel.test.ts +51 -0
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.js +14 -4
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/loadImageAsBase64.d.ts +3 -0
- package/ts_build/src/agents/tools/loadImageAsBase64.js +79 -0
- package/ts_build/src/agents/tools/loadImageAsBase64.js.map +1 -0
- package/ts_build/src/auth/keyAuth.js +5 -5
- package/ts_build/src/auth/keyAuth.js.map +1 -1
- package/ts_build/src/chat/modules/AgentModule.js +3 -3
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.d.ts +1 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js +19 -7
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -0
- package/ts_build/src/clients/anthropic.js +83 -38
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/attribution.d.ts +2 -0
- package/ts_build/src/clients/attribution.js +32 -0
- package/ts_build/src/clients/attribution.js.map +1 -0
- package/ts_build/src/clients/http.js +100 -20
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +2 -1
- package/ts_build/src/clients/index.js +6 -2
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/openai.d.ts +2 -1
- package/ts_build/src/clients/openai.js +225 -99
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/openrouter.d.ts +2 -2
- package/ts_build/src/clients/openrouter.js +6 -2
- package/ts_build/src/clients/openrouter.js.map +1 -1
- package/ts_build/src/clients/pricing/openai.js +15 -1
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/types.d.ts +6 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -1
- package/ts_build/src/clients/responseMetadata.d.ts +15 -0
- package/ts_build/src/clients/responseMetadata.js +141 -0
- package/ts_build/src/clients/responseMetadata.js.map +1 -0
- package/ts_build/src/clients/types.d.ts +18 -1
- package/ts_build/src/clients/types.js +2 -1
- package/ts_build/src/clients/types.js.map +1 -1
- package/ts_build/src/commands/services.js +15 -8
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/login.js +19 -5
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.js +7 -3
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -1
- package/ts_build/src/tunnel.js +9 -1
- package/ts_build/src/tunnel.js.map +1 -1
- package/ts_build/tests/processors/Base64ImageDetector.test.js +28 -0
- package/ts_build/tests/processors/Base64ImageDetector.test.js.map +1 -1
- package/ts_build/tests/unit/agents/tools/loadImageAsBase64.test.d.ts +1 -0
- package/ts_build/tests/unit/agents/tools/loadImageAsBase64.test.js +67 -0
- package/ts_build/tests/unit/agents/tools/loadImageAsBase64.test.js.map +1 -0
- package/ts_build/tests/unit/auth/keyAuth.test.js +21 -0
- package/ts_build/tests/unit/auth/keyAuth.test.js.map +1 -1
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js +77 -0
- package/ts_build/tests/unit/clients/anthropic.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/attribution.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/attribution.test.js +153 -0
- package/ts_build/tests/unit/clients/attribution.test.js.map +1 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.js +77 -0
- package/ts_build/tests/unit/clients/http.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.js +86 -0
- package/ts_build/tests/unit/clients/openai.responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.d.ts +1 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.js +96 -0
- package/ts_build/tests/unit/clients/responseMetadata.test.js.map +1 -0
- package/ts_build/tests/unit/login.test.js +29 -1
- package/ts_build/tests/unit/login.test.js.map +1 -1
- package/ts_build/tests/unit/services/Mcp.test.d.ts +1 -0
- package/ts_build/tests/unit/services/Mcp.test.js +45 -0
- package/ts_build/tests/unit/services/Mcp.test.js.map +1 -0
- package/ts_build/tests/unit/tunnel.test.d.ts +1 -0
- package/ts_build/tests/unit/tunnel.test.js +47 -0
- package/ts_build/tests/unit/tunnel.test.js.map +1 -0
package/src/clients/anthropic.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import { wait } from "../utils";
|
|
3
|
+
import { emitResponseMetadata } from "./responseMetadata";
|
|
3
4
|
import { AnthropicTextPricing, AnthropicModels } from "./pricing";
|
|
4
5
|
import { ContextLimits } from "./contextLimits";
|
|
5
6
|
import { ModelModality } from "./types";
|
|
@@ -27,12 +28,16 @@ type Usage = Anthropic.Usage;
|
|
|
27
28
|
type ThinkingParam = Anthropic.Messages.ThinkingConfigParam;
|
|
28
29
|
|
|
29
30
|
const alwaysOnModels: readonly string[] = [
|
|
30
|
-
AnthropicModels.Sonnet5,
|
|
31
|
-
AnthropicModels.
|
|
31
|
+
AnthropicModels.Sonnet5,
|
|
32
|
+
AnthropicModels.Fable5,
|
|
33
|
+
AnthropicModels.Mythos5,
|
|
34
|
+
AnthropicModels.MythosPreview,
|
|
32
35
|
];
|
|
33
36
|
const optInModels: readonly string[] = [
|
|
34
|
-
AnthropicModels.Opus4_8,
|
|
35
|
-
AnthropicModels.
|
|
37
|
+
AnthropicModels.Opus4_8,
|
|
38
|
+
AnthropicModels.Opus4_7,
|
|
39
|
+
AnthropicModels.Opus4_6,
|
|
40
|
+
AnthropicModels.Sonnet4_6,
|
|
36
41
|
];
|
|
37
42
|
|
|
38
43
|
export class GenericAnthropicClient implements GenericClient {
|
|
@@ -43,10 +48,32 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
43
48
|
this.setKey(apiKey || process.env.ANTHROPIC_API_KEY || "");
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
private emitErrorMetadata(options: CompletionOptions, error: unknown) {
|
|
52
|
+
if (!error || typeof error !== "object") return;
|
|
53
|
+
const apiError = error as {
|
|
54
|
+
status?: number;
|
|
55
|
+
headers?: Headers | Record<string, unknown>;
|
|
56
|
+
request_id?: string;
|
|
57
|
+
requestID?: string;
|
|
58
|
+
response?: Response;
|
|
59
|
+
};
|
|
60
|
+
const statusCode = apiError.status ?? apiError.response?.status;
|
|
61
|
+
const headers = apiError.headers ?? apiError.response?.headers;
|
|
62
|
+
if (typeof statusCode === "number" || headers) {
|
|
63
|
+
emitResponseMetadata(options, {
|
|
64
|
+
statusCode,
|
|
65
|
+
headers,
|
|
66
|
+
requestId: apiError.request_id ?? apiError.requestID,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
46
71
|
setKey(apiKey: string) {
|
|
47
72
|
this.apiKey = apiKey;
|
|
48
73
|
this.client = new Anthropic({
|
|
49
74
|
apiKey: apiKey || process.env.ANTHROPIC_API_KEY,
|
|
75
|
+
// Do not hide retry responses from the response-scoped observer.
|
|
76
|
+
maxRetries: 0,
|
|
50
77
|
});
|
|
51
78
|
}
|
|
52
79
|
|
|
@@ -54,7 +81,9 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
54
81
|
const lastTool = tools[tools.length - 1];
|
|
55
82
|
|
|
56
83
|
if (lastTool) {
|
|
57
|
-
lastTool.cache_control = longTtl
|
|
84
|
+
lastTool.cache_control = longTtl
|
|
85
|
+
? ({ type: "ephemeral", ttl: "1h" } as any)
|
|
86
|
+
: { type: "ephemeral" };
|
|
58
87
|
}
|
|
59
88
|
}
|
|
60
89
|
|
|
@@ -65,10 +94,12 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
65
94
|
getThinkingParam(
|
|
66
95
|
model: string,
|
|
67
96
|
reasoning_effort?: CompletionOptions["reasoning_effort"],
|
|
68
|
-
reasoning_summary?: boolean
|
|
97
|
+
reasoning_summary?: boolean
|
|
69
98
|
): ThinkingParam | undefined {
|
|
70
99
|
const effortIsNone = reasoning_effort === "none";
|
|
71
|
-
const withSummary = reasoning_summary
|
|
100
|
+
const withSummary = reasoning_summary
|
|
101
|
+
? ({ display: "summarized" } as const)
|
|
102
|
+
: {};
|
|
72
103
|
|
|
73
104
|
if (alwaysOnModels.includes(model)) {
|
|
74
105
|
return effortIsNone
|
|
@@ -112,13 +143,13 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
112
143
|
* Removes unsupported fields like additionalProperties, $ref, $defs, positional.
|
|
113
144
|
*/
|
|
114
145
|
private cleanSchemaForAnthropic(schema: any): any {
|
|
115
|
-
if (!schema || typeof schema !==
|
|
146
|
+
if (!schema || typeof schema !== "object") {
|
|
116
147
|
return schema;
|
|
117
148
|
}
|
|
118
149
|
|
|
119
150
|
// Handle arrays
|
|
120
151
|
if (Array.isArray(schema)) {
|
|
121
|
-
return schema.map(item => this.cleanSchemaForAnthropic(item));
|
|
152
|
+
return schema.map((item) => this.cleanSchemaForAnthropic(item));
|
|
122
153
|
}
|
|
123
154
|
|
|
124
155
|
const cleaned: any = {};
|
|
@@ -130,10 +161,10 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
130
161
|
|
|
131
162
|
// Skip unsupported properties
|
|
132
163
|
if (
|
|
133
|
-
key ===
|
|
134
|
-
key ===
|
|
135
|
-
key ===
|
|
136
|
-
key ===
|
|
164
|
+
key === "additionalProperties" ||
|
|
165
|
+
key === "$ref" ||
|
|
166
|
+
key === "$defs" ||
|
|
167
|
+
key === "positional"
|
|
137
168
|
) {
|
|
138
169
|
continue;
|
|
139
170
|
}
|
|
@@ -141,7 +172,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
141
172
|
const value = schema[key];
|
|
142
173
|
|
|
143
174
|
// Recursively clean nested objects
|
|
144
|
-
if (typeof value ===
|
|
175
|
+
if (typeof value === "object" && value !== null) {
|
|
145
176
|
cleaned[key] = this.cleanSchemaForAnthropic(value);
|
|
146
177
|
}
|
|
147
178
|
// Copy primitive values as-is
|
|
@@ -160,7 +191,9 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
160
191
|
const transformed = tools.map((tool) => ({
|
|
161
192
|
name: tool.function.name || "",
|
|
162
193
|
description: tool.function.description || "",
|
|
163
|
-
input_schema: this.cleanSchemaForAnthropic(
|
|
194
|
+
input_schema: this.cleanSchemaForAnthropic(
|
|
195
|
+
tool.function.parameters
|
|
196
|
+
) as any,
|
|
164
197
|
}));
|
|
165
198
|
|
|
166
199
|
this.handleToolCaching(transformed, longTtl);
|
|
@@ -440,7 +473,7 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
440
473
|
const thinkingParam = this.getThinkingParam(
|
|
441
474
|
options.model,
|
|
442
475
|
options.reasoning_effort,
|
|
443
|
-
options.reasoning_summary
|
|
476
|
+
options.reasoning_summary
|
|
444
477
|
);
|
|
445
478
|
const effortOutputConfig = this.getEffortOutputConfig(
|
|
446
479
|
options.model,
|
|
@@ -456,28 +489,41 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
456
489
|
|
|
457
490
|
const tools = this.transformTools(options.tools, longTtl);
|
|
458
491
|
try {
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
492
|
+
const result = await this.client.messages
|
|
493
|
+
.create(
|
|
494
|
+
{
|
|
495
|
+
model: options.model,
|
|
496
|
+
messages: claudeMessages,
|
|
497
|
+
system: systemMessage
|
|
498
|
+
? [
|
|
499
|
+
{
|
|
500
|
+
text: systemMessage,
|
|
501
|
+
cache_control: longTtl
|
|
502
|
+
? ({ type: "ephemeral", ttl: "1h" } as any)
|
|
503
|
+
: { type: "ephemeral" },
|
|
504
|
+
type: "text",
|
|
505
|
+
},
|
|
506
|
+
]
|
|
507
|
+
: undefined,
|
|
508
|
+
max_tokens: options.max_tokens || 8000,
|
|
509
|
+
...(thinkingParam && { thinking: thinkingParam }),
|
|
510
|
+
...(effortOutputConfig && { output_config: effortOutputConfig }),
|
|
511
|
+
...(tools.length && {
|
|
512
|
+
tool_choice: {
|
|
513
|
+
type: options.tool_choice ?? "auto",
|
|
468
514
|
},
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
max_tokens: options.max_tokens || 8000,
|
|
472
|
-
...(thinkingParam && { thinking: thinkingParam }),
|
|
473
|
-
...(effortOutputConfig && { output_config: effortOutputConfig }),
|
|
474
|
-
...(tools.length && {
|
|
475
|
-
tool_choice: {
|
|
476
|
-
type: options.tool_choice ?? "auto",
|
|
515
|
+
tools,
|
|
516
|
+
}),
|
|
477
517
|
},
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
518
|
+
{ signal: options.signal }
|
|
519
|
+
)
|
|
520
|
+
.withResponse();
|
|
521
|
+
const { data: response, response: nativeResponse, request_id } = result;
|
|
522
|
+
emitResponseMetadata(options, {
|
|
523
|
+
statusCode: nativeResponse.status,
|
|
524
|
+
headers: nativeResponse.headers,
|
|
525
|
+
requestId: request_id,
|
|
526
|
+
});
|
|
481
527
|
|
|
482
528
|
if (!response.content || !response.content.length) {
|
|
483
529
|
console.log("no content in Anthropic response", response);
|
|
@@ -496,10 +542,11 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
496
542
|
? { provider: "anthropic", items: thinkingBlocks }
|
|
497
543
|
: undefined;
|
|
498
544
|
// Extract a human-readable summary from thinking blocks (when present).
|
|
499
|
-
const reasoningSummary =
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
545
|
+
const reasoningSummary =
|
|
546
|
+
thinkingBlocks
|
|
547
|
+
.map((b) => ("thinking" in b ? (b as any).thinking : ""))
|
|
548
|
+
.filter(Boolean)
|
|
549
|
+
.join("\n") || undefined;
|
|
503
550
|
|
|
504
551
|
// Determine which resulting choice should carry the thinking blocks. They
|
|
505
552
|
// are re-injected before the first tool_use on the next turn, so prefer
|
|
@@ -509,16 +556,18 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
509
556
|
const nonThinking = response.content.filter(
|
|
510
557
|
(c) => c.type !== "thinking" && c.type !== "redacted_thinking"
|
|
511
558
|
);
|
|
512
|
-
const firstToolUseIdx = nonThinking.findIndex(
|
|
559
|
+
const firstToolUseIdx = nonThinking.findIndex(
|
|
560
|
+
(c) => c.type === "tool_use"
|
|
561
|
+
);
|
|
513
562
|
const carryIdx = firstToolUseIdx >= 0 ? firstToolUseIdx : 0;
|
|
514
563
|
|
|
515
564
|
return {
|
|
516
565
|
choices: nonThinking.map((c, idx) => {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
566
|
+
// Only the FIRST resulting assistant message carries the thinking
|
|
567
|
+
// blocks (Anthropic requires a single thinking block sequence at the
|
|
568
|
+
// start of the assistant turn); attaching to every choice would
|
|
569
|
+
// duplicate them once the messages are re-transformed.
|
|
570
|
+
const carryReasoning = idx === carryIdx;
|
|
522
571
|
if (c.type === "tool_use") {
|
|
523
572
|
return {
|
|
524
573
|
message: {
|
|
@@ -534,8 +583,10 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
534
583
|
},
|
|
535
584
|
},
|
|
536
585
|
],
|
|
537
|
-
...(carryReasoning &&
|
|
538
|
-
|
|
586
|
+
...(carryReasoning &&
|
|
587
|
+
reasoningDetails && { _reasoning_details: reasoningDetails }),
|
|
588
|
+
...(carryReasoning &&
|
|
589
|
+
reasoningSummary && { reasoning_summary: reasoningSummary }),
|
|
539
590
|
},
|
|
540
591
|
};
|
|
541
592
|
} else {
|
|
@@ -544,36 +595,48 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
544
595
|
role: "assistant",
|
|
545
596
|
content: "text" in c ? c.text : c.type,
|
|
546
597
|
tool_calls: [],
|
|
547
|
-
...(carryReasoning &&
|
|
548
|
-
|
|
598
|
+
...(carryReasoning &&
|
|
599
|
+
reasoningDetails && { _reasoning_details: reasoningDetails }),
|
|
600
|
+
...(carryReasoning &&
|
|
601
|
+
reasoningSummary && { reasoning_summary: reasoningSummary }),
|
|
549
602
|
},
|
|
550
603
|
};
|
|
551
604
|
}
|
|
552
605
|
}),
|
|
553
606
|
|
|
554
607
|
model: options.model,
|
|
555
|
-
usage: response.usage
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
608
|
+
usage: response.usage
|
|
609
|
+
? {
|
|
610
|
+
input_tokens: response.usage.input_tokens ?? 0,
|
|
611
|
+
output_tokens: response.usage.output_tokens ?? 0,
|
|
612
|
+
prompt_tokens: response.usage.input_tokens ?? 0,
|
|
613
|
+
completion_tokens: response.usage.output_tokens ?? 0,
|
|
614
|
+
total_tokens:
|
|
615
|
+
(response.usage.input_tokens ?? 0) +
|
|
616
|
+
(response.usage.output_tokens ?? 0),
|
|
617
|
+
cache_creation_input_tokens:
|
|
618
|
+
response.usage.cache_creation_input_tokens ?? 0,
|
|
619
|
+
cache_read_input_tokens:
|
|
620
|
+
response.usage.cache_read_input_tokens ?? 0,
|
|
621
|
+
...((response.usage as any).thinking_input_tokens != null ||
|
|
622
|
+
(response.usage as any).output_tokens_details?.thinking_tokens !=
|
|
623
|
+
null
|
|
624
|
+
? {
|
|
625
|
+
output_tokens_details: {
|
|
626
|
+
thinking_tokens:
|
|
627
|
+
(response.usage as any).thinking_input_tokens ??
|
|
628
|
+
(response.usage as any).output_tokens_details
|
|
629
|
+
?.thinking_tokens ??
|
|
630
|
+
0,
|
|
631
|
+
},
|
|
632
|
+
}
|
|
633
|
+
: {}),
|
|
634
|
+
}
|
|
635
|
+
: undefined,
|
|
574
636
|
usd_cost: this.calculateCost(options.model, response.usage),
|
|
575
637
|
};
|
|
576
638
|
} catch (err) {
|
|
639
|
+
this.emitErrorMetadata(options, err);
|
|
577
640
|
if ("headers" in err && err.headers?.["x-should-retry"] === "true") {
|
|
578
641
|
console.warn("Retrying failed request", err);
|
|
579
642
|
await wait(2500);
|
|
@@ -589,25 +652,37 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
589
652
|
return AnthropicTextPricing;
|
|
590
653
|
}
|
|
591
654
|
|
|
592
|
-
getPricing(
|
|
655
|
+
getPricing(
|
|
656
|
+
model?: string
|
|
657
|
+
):
|
|
658
|
+
| import("./pricing/types").ModelPricing
|
|
659
|
+
| Record<string, import("./pricing/types").ModelPricing>
|
|
660
|
+
| undefined {
|
|
593
661
|
if (model !== undefined) {
|
|
594
|
-
return AnthropicTextPricing[
|
|
662
|
+
return AnthropicTextPricing[
|
|
663
|
+
model as keyof typeof AnthropicTextPricing
|
|
664
|
+
] as import("./pricing/types").ModelPricing | undefined;
|
|
595
665
|
}
|
|
596
|
-
return AnthropicTextPricing as unknown as Record<
|
|
666
|
+
return AnthropicTextPricing as unknown as Record<
|
|
667
|
+
string,
|
|
668
|
+
import("./pricing/types").ModelPricing
|
|
669
|
+
>;
|
|
597
670
|
}
|
|
598
671
|
|
|
599
672
|
calculateCost(model: string, usage: Usage): number | undefined {
|
|
600
673
|
const rawP = this.pricesPerMillion()[model];
|
|
601
674
|
// Fall back to pricing file for unknown/newer models
|
|
602
|
-
const fallback =
|
|
675
|
+
const fallback =
|
|
676
|
+
AnthropicTextPricing[model as keyof typeof AnthropicTextPricing];
|
|
603
677
|
// Second fallback: short aliases like "claude-sonnet-4-5" don't have their
|
|
604
678
|
// own entry but map to a dated version like "claude-sonnet-4-5-20250929".
|
|
605
679
|
// Find the first pricing entry whose key starts with the short model name.
|
|
606
|
-
const aliasFallback =
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
680
|
+
const aliasFallback =
|
|
681
|
+
!rawP && !fallback
|
|
682
|
+
? Object.entries(AnthropicTextPricing).find(([key]) =>
|
|
683
|
+
key.startsWith(model + "-")
|
|
684
|
+
)?.[1]
|
|
685
|
+
: undefined;
|
|
611
686
|
const p: any = rawP || fallback || aliasFallback || undefined;
|
|
612
687
|
if (!p) return undefined;
|
|
613
688
|
|
|
@@ -663,7 +738,9 @@ export class GenericAnthropicClient implements GenericClient {
|
|
|
663
738
|
throw new Error("Anthropic does not support audio transcription");
|
|
664
739
|
}
|
|
665
740
|
|
|
666
|
-
getContextLimit(
|
|
741
|
+
getContextLimit(
|
|
742
|
+
model: string
|
|
743
|
+
): { contextLimit: number; threshold: number } | undefined {
|
|
667
744
|
const contextLimit = ContextLimits[model];
|
|
668
745
|
if (contextLimit === undefined) return undefined;
|
|
669
746
|
const pricing = AnthropicTextPricing[model];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import packageJson from "../../package.json";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default Knowhow attribution headers.
|
|
5
|
+
*
|
|
6
|
+
* - `HTTP-Referer` – OpenRouter analytics / billing attribution.
|
|
7
|
+
* - `X-Title` – Generic app-name header; supported by OpenRouter
|
|
8
|
+
* and other providers that follow the same convention.
|
|
9
|
+
* - `X-OpenRouter-Title` – Legacy OpenRouter-specific alias (kept for
|
|
10
|
+
* back-compat; `X-Title` is preferred going forward).
|
|
11
|
+
* - `User-Agent` – Standard SDK identification.
|
|
12
|
+
*/
|
|
13
|
+
export const KNOWHOW_ATTRIBUTION_HEADERS: Readonly<Record<string, string>> = Object.freeze({
|
|
14
|
+
"HTTP-Referer": "https://knowhow.tyvm.ai",
|
|
15
|
+
"X-Title": "Knowhow",
|
|
16
|
+
"X-OpenRouter-Title": "Knowhow",
|
|
17
|
+
"User-Agent": `knowhow/${packageJson.version}`,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Adds Knowhow's application attribution to an outbound OpenRouter request.
|
|
22
|
+
* Later header sets win case-insensitively, so callers can override defaults
|
|
23
|
+
* without producing duplicate keys.
|
|
24
|
+
*/
|
|
25
|
+
export function withKnowhowAttribution(
|
|
26
|
+
/**
|
|
27
|
+
* Common overrides:
|
|
28
|
+
* - `"X-Title"` / `"X-OpenRouter-Title"` – set your own app name
|
|
29
|
+
* - `"HTTP-Referer"` – set your own referer URL
|
|
30
|
+
* - any additional custom headers are merged in as-is
|
|
31
|
+
*/
|
|
32
|
+
...headerSets: Array<Record<string, string> | undefined>
|
|
33
|
+
): Record<string, string> {
|
|
34
|
+
const result: Record<string, string> = { ...KNOWHOW_ATTRIBUTION_HEADERS };
|
|
35
|
+
|
|
36
|
+
for (const headers of headerSets) {
|
|
37
|
+
if (!headers) continue;
|
|
38
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
39
|
+
const existingKey = Object.keys(result).find(
|
|
40
|
+
(candidate) => candidate.toLowerCase() === key.toLowerCase()
|
|
41
|
+
);
|
|
42
|
+
if (existingKey) {
|
|
43
|
+
// Keep the original casing so SDK-owned defaults such as User-Agent
|
|
44
|
+
// are replaced rather than duplicated in their intermediate objects.
|
|
45
|
+
result[existingKey] = value;
|
|
46
|
+
} else {
|
|
47
|
+
result[key] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return result;
|
|
53
|
+
}
|