@tyvm/knowhow 0.0.156 → 0.0.158
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 +17 -1
- package/package.json +1 -1
- package/src/agents/base/base.ts +176 -39
- package/src/auth/browserLogin.ts +2 -13
- package/src/auth/environmentAuth.ts +51 -0
- package/src/auth/jwtStore.ts +70 -0
- package/src/auth/keyAuth.ts +4 -15
- package/src/auth/registerIdentity.ts +8 -8
- package/src/chat/modules/AgentModule.ts +3 -3
- package/src/chat/modules/RemoteSyncModule.ts +45 -17
- package/src/cli.ts +2 -0
- 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 +45 -13
- package/src/clients/openai.ts +441 -220
- package/src/clients/openrouter.ts +10 -3
- package/src/clients/pricing/fireworks.ts +32 -2
- 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/mcp.ts +1 -0
- package/src/commands/misc.ts +1 -1
- package/src/commands/remote.ts +77 -0
- package/src/config.ts +9 -1
- package/src/login.ts +54 -42
- package/src/remotes.ts +110 -0
- package/src/services/KnowhowClient.ts +12 -16
- package/src/services/Mcp.ts +94 -7
- package/src/services/McpWebsocketTransport.ts +14 -11
- package/src/types.ts +12 -0
- package/tests/unit/McpWebsocketTransport.test.ts +56 -0
- package/tests/unit/auth/jwtStore.test.ts +55 -0
- package/tests/unit/auth/registerIdentity.test.ts +8 -2
- 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/commands/remote.test.ts +79 -0
- package/tests/unit/login.test.ts +64 -3
- package/tests/unit/services/KnowhowClient.test.ts +2 -2
- package/tests/unit/services/Mcp.test.ts +2 -2
- package/ts_build/package.json +1 -1
- package/ts_build/src/agents/base/base.d.ts +8 -3
- package/ts_build/src/agents/base/base.js +112 -26
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/auth/browserLogin.js +2 -7
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/auth/environmentAuth.d.ts +5 -0
- package/ts_build/src/auth/environmentAuth.js +48 -0
- package/ts_build/src/auth/environmentAuth.js.map +1 -0
- package/ts_build/src/auth/jwtStore.d.ts +4 -0
- package/ts_build/src/auth/jwtStore.js +65 -0
- package/ts_build/src/auth/jwtStore.js.map +1 -0
- package/ts_build/src/auth/keyAuth.d.ts +1 -1
- package/ts_build/src/auth/keyAuth.js +4 -16
- package/ts_build/src/auth/keyAuth.js.map +1 -1
- package/ts_build/src/auth/registerIdentity.js +6 -7
- package/ts_build/src/auth/registerIdentity.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/cli.js +2 -0
- package/ts_build/src/cli.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 +24 -6
- 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/fireworks.d.ts +4 -0
- package/ts_build/src/clients/pricing/fireworks.js +14 -2
- package/ts_build/src/clients/pricing/fireworks.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/mcp.js.map +1 -1
- package/ts_build/src/commands/misc.js +1 -1
- package/ts_build/src/commands/misc.js.map +1 -1
- package/ts_build/src/commands/remote.d.ts +2 -0
- package/ts_build/src/commands/remote.js +70 -0
- package/ts_build/src/commands/remote.js.map +1 -0
- package/ts_build/src/config.js +8 -0
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/login.d.ts +4 -3
- package/ts_build/src/login.js +34 -35
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/remotes.d.ts +14 -0
- package/ts_build/src/remotes.js +117 -0
- package/ts_build/src/remotes.js.map +1 -0
- package/ts_build/src/services/KnowhowClient.d.ts +1 -1
- package/ts_build/src/services/KnowhowClient.js +12 -14
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/Mcp.d.ts +1 -1
- package/ts_build/src/services/Mcp.js +68 -7
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/McpWebsocketTransport.d.ts +1 -0
- package/ts_build/src/services/McpWebsocketTransport.js +11 -6
- package/ts_build/src/services/McpWebsocketTransport.js.map +1 -1
- package/ts_build/src/types.d.ts +13 -0
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/tests/unit/McpWebsocketTransport.test.d.ts +1 -0
- package/ts_build/tests/unit/McpWebsocketTransport.test.js +47 -0
- package/ts_build/tests/unit/McpWebsocketTransport.test.js.map +1 -0
- package/ts_build/tests/unit/auth/jwtStore.test.d.ts +1 -0
- package/ts_build/tests/unit/auth/jwtStore.test.js +46 -0
- package/ts_build/tests/unit/auth/jwtStore.test.js.map +1 -0
- package/ts_build/tests/unit/auth/registerIdentity.test.js +8 -2
- package/ts_build/tests/unit/auth/registerIdentity.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/commands/remote.test.d.ts +1 -0
- package/ts_build/tests/unit/commands/remote.test.js +70 -0
- package/ts_build/tests/unit/commands/remote.test.js.map +1 -0
- package/ts_build/tests/unit/login.test.js +44 -3
- package/ts_build/tests/unit/login.test.js.map +1 -1
- package/ts_build/tests/unit/services/KnowhowClient.test.js +2 -2
- package/ts_build/tests/unit/services/KnowhowClient.test.js.map +1 -1
- package/ts_build/tests/unit/services/Mcp.test.js +2 -2
- package/ts_build/tests/unit/services/Mcp.test.js.map +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { HttpClient } from "./http";
|
|
1
|
+
import { HttpClient, HttpClientOptions } from "./http";
|
|
2
|
+
import { withKnowhowAttribution } from "./attribution";
|
|
2
3
|
import { OpenRouterTextPricing } from "./pricing/openrouter";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -9,8 +10,14 @@ import { OpenRouterTextPricing } from "./pricing/openrouter";
|
|
|
9
10
|
* Set env var OPENROUTER_API_KEY to enable.
|
|
10
11
|
*/
|
|
11
12
|
export class GenericOpenRouterClient extends HttpClient {
|
|
12
|
-
constructor(
|
|
13
|
-
|
|
13
|
+
constructor(
|
|
14
|
+
apiKey = process.env.OPENROUTER_API_KEY,
|
|
15
|
+
transportOptions: HttpClientOptions = {}
|
|
16
|
+
) {
|
|
17
|
+
super("https://openrouter.ai/api", {
|
|
18
|
+
...transportOptions,
|
|
19
|
+
headers: withKnowhowAttribution(transportOptions.headers),
|
|
20
|
+
});
|
|
14
21
|
if (apiKey) this.setJwt(apiKey);
|
|
15
22
|
this.setPrices(OpenRouterTextPricing);
|
|
16
23
|
}
|
|
@@ -11,6 +11,10 @@ import { ModelPricing, ModelCatalogEntry, completions, embeddings } from "./type
|
|
|
11
11
|
export const FireworksModels = {
|
|
12
12
|
// Moonshot AI
|
|
13
13
|
KimiK3: "accounts/fireworks/models/kimi-k3",
|
|
14
|
+
|
|
15
|
+
// Meta
|
|
16
|
+
MuseGlimmer30b: "accounts/fireworks/models/muse-glimmer-30b",
|
|
17
|
+
|
|
14
18
|
KimiK2p7Code: "accounts/fireworks/models/kimi-k2p7-code",
|
|
15
19
|
KimiK2_6: "accounts/fireworks/models/kimi-k2-6",
|
|
16
20
|
KimiK2_5: "accounts/fireworks/models/kimi-k2-5",
|
|
@@ -22,6 +26,13 @@ export const FireworksModels = {
|
|
|
22
26
|
|
|
23
27
|
// Qwen
|
|
24
28
|
Qwen3p7Plus: "accounts/fireworks/models/qwen3p7-plus",
|
|
29
|
+
Qwen3p8Max: "accounts/fireworks/models/qwen3p8-2p4t-a95b",
|
|
30
|
+
|
|
31
|
+
// NVIDIA
|
|
32
|
+
NemotronLightning3p530bA3b: "accounts/fireworks/models/nemotron-lightning-3p5-30b-a3b",
|
|
33
|
+
|
|
34
|
+
// Thinking Machines Lab
|
|
35
|
+
Inkling: "accounts/fireworks/models/inkling",
|
|
25
36
|
|
|
26
37
|
// Deprecated Qwen Models
|
|
27
38
|
Qwen3_6Plus: "accounts/fireworks/models/qwen3-6-plus",
|
|
@@ -56,6 +67,10 @@ export const FireworksTextPricing: Record<string, ModelPricing> = {
|
|
|
56
67
|
// Moonshot AI
|
|
57
68
|
[FireworksModels.KimiK3]: { input: 3.0, cache_hit: 0.30, output: 15.0 },
|
|
58
69
|
[FireworksModels.KimiK2p7Code]: { input: 0.95, cache_hit: 0.19, output: 4.0 },
|
|
70
|
+
|
|
71
|
+
// Meta
|
|
72
|
+
[FireworksModels.MuseGlimmer30b]: { input: 0.35, cache_hit: 0.04, output: 1.50 },
|
|
73
|
+
|
|
59
74
|
[FireworksModels.KimiK2_6]: { input: 0.95, cache_hit: 0.16, output: 4.0 },
|
|
60
75
|
[FireworksModels.KimiK2_5]: { input: 0.60, cache_hit: 0.10, output: 3.0 },
|
|
61
76
|
|
|
@@ -67,6 +82,14 @@ export const FireworksTextPricing: Record<string, ModelPricing> = {
|
|
|
67
82
|
// Qwen
|
|
68
83
|
[FireworksModels.Qwen3p7Plus]: { input: 0.40, cache_hit: 0.08, output: 1.60 },
|
|
69
84
|
|
|
85
|
+
[FireworksModels.Qwen3p8Max]: { input: 2.00, cache_hit: 0.25, output: 6.00 },
|
|
86
|
+
|
|
87
|
+
// NVIDIA
|
|
88
|
+
[FireworksModels.NemotronLightning3p530bA3b]: { input: 0.05, cache_hit: 0.01, output: 0.20 },
|
|
89
|
+
|
|
90
|
+
// Thinking Machines Lab
|
|
91
|
+
[FireworksModels.Inkling]: { input: 1.00, cache_hit: 0.17, output: 4.05 },
|
|
92
|
+
|
|
70
93
|
// Deprecated QWEN Models
|
|
71
94
|
[FireworksModels.Qwen3_6Plus]: { input: 0.50, cache_hit: 0.10, output: 3.0, deprecated: true },
|
|
72
95
|
[FireworksModels.Qwen3p6Plus]: { input: 0.50, cache_hit: 0.10, output: 3.0, deprecated: true },
|
|
@@ -81,8 +104,8 @@ export const FireworksTextPricing: Record<string, ModelPricing> = {
|
|
|
81
104
|
[FireworksModels.Glm4_7]: { input: 0.60, cache_hit: 0.30, output: 2.20, deprecated: true },
|
|
82
105
|
|
|
83
106
|
// DeepSeek AI
|
|
84
|
-
[FireworksModels.DeepseekV4Pro]: { input: 1.
|
|
85
|
-
[FireworksModels.DeepseekV4Flash]: { input: 0.
|
|
107
|
+
[FireworksModels.DeepseekV4Pro]: { input: 1.32, cache_hit: 0.044, output: 3.96 },
|
|
108
|
+
[FireworksModels.DeepseekV4Flash]: { input: 0.22, cache_hit: 0.007, output: 0.66 },
|
|
86
109
|
[FireworksModels.DeepseekV3_2]: { input: 0.56, cache_hit: 0.28, output: 1.68, deprecated: true },
|
|
87
110
|
[FireworksModels.DeepseekV3_1]: { input: 0.56, cache_hit: 0.28, output: 1.68, deprecated: true },
|
|
88
111
|
|
|
@@ -111,12 +134,19 @@ export const FireworksActiveTextModels: string[] = [
|
|
|
111
134
|
FireworksModels.KimiK2p7Code,
|
|
112
135
|
FireworksModels.KimiK2_6,
|
|
113
136
|
FireworksModels.KimiK2_5,
|
|
137
|
+
// Meta
|
|
138
|
+
FireworksModels.MuseGlimmer30b,
|
|
114
139
|
// MiniMax
|
|
115
140
|
FireworksModels.MinimaxM3,
|
|
116
141
|
FireworksModels.MinimaxM2_7,
|
|
117
142
|
FireworksModels.MinimaxM2_5,
|
|
118
143
|
// Qwen
|
|
119
144
|
FireworksModels.Qwen3p7Plus,
|
|
145
|
+
FireworksModels.Qwen3p8Max,
|
|
146
|
+
// NVIDIA
|
|
147
|
+
FireworksModels.NemotronLightning3p530bA3b,
|
|
148
|
+
// Thinking Machines Lab
|
|
149
|
+
FireworksModels.Inkling,
|
|
120
150
|
// Z.ai
|
|
121
151
|
FireworksModels.Glm5p2,
|
|
122
152
|
FireworksModels.Glm5p1,
|
|
@@ -231,7 +231,21 @@ export const OpenAiSearchModels: string[] = [
|
|
|
231
231
|
|
|
232
232
|
export const OpenAiTextPricing: Record<string, ModelPricing> = {
|
|
233
233
|
// gpt-5.6 series — short context / long context pricing
|
|
234
|
-
[OpenAiModels.GPT_56_Sol]:
|
|
234
|
+
[OpenAiModels.GPT_56_Sol]: {
|
|
235
|
+
input: 4.0,
|
|
236
|
+
cached_input: 0.40,
|
|
237
|
+
cache_write: 5.00,
|
|
238
|
+
output: 30.0,
|
|
239
|
+
input_gt_200k: 10.0,
|
|
240
|
+
output_gt_200k: 30.0,
|
|
241
|
+
changelog: [
|
|
242
|
+
{
|
|
243
|
+
date: "2026-08-01",
|
|
244
|
+
note: "Launch pricing — promotional rates available through November 21, 2026",
|
|
245
|
+
pricing: { input: 5.0, cached_input: 0.5, cache_write: 6.25, output: 30.0, input_gt_200k: 10.0, output_gt_200k: 45.0 },
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
235
249
|
[OpenAiModels.GPT_56_Terra]: { input: 2.0, cached_input: 0.20, cache_write: 2.5, output: 12.0, input_gt_200k: 4.0, output_gt_200k: 18.0 },
|
|
236
250
|
[OpenAiModels.GPT_56_Luna]: { input: 0.2, cached_input: 0.02, cache_write: 0.25, output: 1.2, input_gt_200k: 0.4, output_gt_200k: 1.8 },
|
|
237
251
|
[OpenAiModels.GPT_55]: { input: 5.0, cached_input: 0.5, output: 30.0 },
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A historical record of a model's pricing at a point in time.
|
|
3
|
+
* Use this to track price changes over time.
|
|
4
|
+
*/
|
|
5
|
+
export interface PricingChangelogEntry {
|
|
6
|
+
/** ISO date string when this pricing became effective (e.g. "2025-04-01") */
|
|
7
|
+
date: string;
|
|
8
|
+
/** Optional human-readable note about the change (e.g. "Promotional pricing announcement") */
|
|
9
|
+
note?: string;
|
|
10
|
+
/** The pricing values that were in effect from this date */
|
|
11
|
+
pricing: Omit<ModelPricing, "deprecated" | "deprecationDate" | "limitedAvailability" | "replacedBy" | "reasoningLevels" | "useResponsesApi" | "changelog">;
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
export type ModelType =
|
|
2
15
|
| "completion"
|
|
3
16
|
| "embedding"
|
|
@@ -30,6 +43,8 @@ export interface ModelPricing {
|
|
|
30
43
|
reasoningLevels?: string[];
|
|
31
44
|
/** If true, this model must be called via the Responses API (/v1/responses) instead of /v1/chat/completions */
|
|
32
45
|
useResponsesApi?: boolean;
|
|
46
|
+
/** Optional changelog of historical pricing entries, ordered from oldest to newest */
|
|
47
|
+
changelog?: PricingChangelogEntry[];
|
|
33
48
|
}
|
|
34
49
|
|
|
35
50
|
export interface ModelCatalogEntry {
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProviderRateLimitMetadata,
|
|
3
|
+
ProviderResponseMetadata,
|
|
4
|
+
RetryOptions,
|
|
5
|
+
} from "./types";
|
|
6
|
+
|
|
7
|
+
const MAX_HEADER_VALUE_LENGTH = 256;
|
|
8
|
+
const MAX_HEADERS = 24;
|
|
9
|
+
const MAX_TOTAL_HEADER_CHARS = 4096;
|
|
10
|
+
const SAFE_HEADER_NAMES = new Set([
|
|
11
|
+
"retry-after",
|
|
12
|
+
"ratelimit-limit",
|
|
13
|
+
"ratelimit-remaining",
|
|
14
|
+
"ratelimit-reset",
|
|
15
|
+
"request-id",
|
|
16
|
+
"x-request-id",
|
|
17
|
+
"cf-ray",
|
|
18
|
+
"x-amzn-requestid",
|
|
19
|
+
"x-amz-request-id",
|
|
20
|
+
]);
|
|
21
|
+
const SAFE_HEADER_PREFIXES = ["x-ratelimit-", "anthropic-ratelimit-"];
|
|
22
|
+
|
|
23
|
+
type HeaderSource =
|
|
24
|
+
| Headers
|
|
25
|
+
| Record<string, unknown>
|
|
26
|
+
| Iterable<[string, unknown]>
|
|
27
|
+
| undefined;
|
|
28
|
+
|
|
29
|
+
function isSafeHeader(name: string) {
|
|
30
|
+
return (
|
|
31
|
+
SAFE_HEADER_NAMES.has(name) ||
|
|
32
|
+
SAFE_HEADER_PREFIXES.some((prefix) => name.startsWith(prefix))
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function entriesFor(headers: HeaderSource): Iterable<[string, unknown]> {
|
|
37
|
+
if (!headers) return [];
|
|
38
|
+
if (Symbol.iterator in Object(headers)) {
|
|
39
|
+
return headers as Iterable<[string, unknown]>;
|
|
40
|
+
}
|
|
41
|
+
if (typeof (headers as Headers).forEach === "function") {
|
|
42
|
+
const entries: Array<[string, unknown]> = [];
|
|
43
|
+
(headers as Headers).forEach((value, name) => entries.push([name, value]));
|
|
44
|
+
return entries;
|
|
45
|
+
}
|
|
46
|
+
return Object.entries(headers as Record<string, unknown>);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function nonNegativeNumber(value: string | undefined) {
|
|
50
|
+
if (value === undefined || value.trim() === "") return undefined;
|
|
51
|
+
const number = Number(value);
|
|
52
|
+
return Number.isFinite(number) && number >= 0 ? number : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resetAt(value: string | undefined, now: Date) {
|
|
56
|
+
if (!value) return undefined;
|
|
57
|
+
const seconds = nonNegativeNumber(value);
|
|
58
|
+
if (seconds !== undefined)
|
|
59
|
+
return new Date(now.getTime() + seconds * 1000).toISOString();
|
|
60
|
+
|
|
61
|
+
// OpenAI-compatible APIs commonly return compact durations such as `6m0s`.
|
|
62
|
+
const duration = [...value.trim().matchAll(/(\d+(?:\.\d+)?)(ms|s|m|h|d)/gi)];
|
|
63
|
+
if (
|
|
64
|
+
duration.length &&
|
|
65
|
+
duration.map((part) => part[0]).join("") === value.trim()
|
|
66
|
+
) {
|
|
67
|
+
const milliseconds = duration.reduce((total, [, amount, unit]) => {
|
|
68
|
+
const multiplier = {
|
|
69
|
+
ms: 1,
|
|
70
|
+
s: 1_000,
|
|
71
|
+
m: 60_000,
|
|
72
|
+
h: 3_600_000,
|
|
73
|
+
d: 86_400_000,
|
|
74
|
+
}[unit.toLowerCase()];
|
|
75
|
+
return total + Number(amount) * multiplier;
|
|
76
|
+
}, 0);
|
|
77
|
+
return new Date(now.getTime() + milliseconds).toISOString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const timestamp = Date.parse(value);
|
|
81
|
+
return Number.isNaN(timestamp)
|
|
82
|
+
? undefined
|
|
83
|
+
: new Date(timestamp).toISOString();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function retryAfterSeconds(value: string | undefined, now: Date) {
|
|
87
|
+
const seconds = nonNegativeNumber(value);
|
|
88
|
+
if (seconds !== undefined) return seconds;
|
|
89
|
+
if (!value) return undefined;
|
|
90
|
+
const timestamp = Date.parse(value);
|
|
91
|
+
return Number.isNaN(timestamp)
|
|
92
|
+
? undefined
|
|
93
|
+
: Math.max(0, (timestamp - now.getTime()) / 1000);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Derives one JSON-safe shape from standard, OpenAI-compatible, and Anthropic
|
|
98
|
+
* rate-limit headers. Callers receive this even when a provider supplied none.
|
|
99
|
+
*/
|
|
100
|
+
export function normalizeRateLimitMetadata(
|
|
101
|
+
headers: Record<string, string>,
|
|
102
|
+
now = new Date()
|
|
103
|
+
): ProviderRateLimitMetadata {
|
|
104
|
+
const header = (...names: string[]) =>
|
|
105
|
+
names.map((name) => headers[name]).find((value) => value !== undefined);
|
|
106
|
+
const limit = nonNegativeNumber(
|
|
107
|
+
header(
|
|
108
|
+
"anthropic-ratelimit-requests-limit",
|
|
109
|
+
"ratelimit-limit",
|
|
110
|
+
"x-ratelimit-limit-requests",
|
|
111
|
+
"x-ratelimit-limit"
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
const remaining = nonNegativeNumber(
|
|
115
|
+
header(
|
|
116
|
+
"anthropic-ratelimit-requests-remaining",
|
|
117
|
+
"ratelimit-remaining",
|
|
118
|
+
"x-ratelimit-remaining-requests",
|
|
119
|
+
"x-ratelimit-remaining"
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
const requestResetAt = resetAt(
|
|
123
|
+
header(
|
|
124
|
+
"anthropic-ratelimit-requests-reset",
|
|
125
|
+
"ratelimit-reset",
|
|
126
|
+
"x-ratelimit-reset-requests",
|
|
127
|
+
"x-ratelimit-reset"
|
|
128
|
+
),
|
|
129
|
+
now
|
|
130
|
+
);
|
|
131
|
+
const tokenLimit = nonNegativeNumber(
|
|
132
|
+
header("anthropic-ratelimit-tokens-limit", "x-ratelimit-limit-tokens")
|
|
133
|
+
);
|
|
134
|
+
const tokenRemaining = nonNegativeNumber(
|
|
135
|
+
header(
|
|
136
|
+
"anthropic-ratelimit-tokens-remaining",
|
|
137
|
+
"x-ratelimit-remaining-tokens"
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
const tokenResetAt = resetAt(
|
|
141
|
+
header("anthropic-ratelimit-tokens-reset", "x-ratelimit-reset-tokens"),
|
|
142
|
+
now
|
|
143
|
+
);
|
|
144
|
+
const retryAfter = retryAfterSeconds(header("retry-after"), now);
|
|
145
|
+
return {
|
|
146
|
+
...(limit !== undefined && { limit }),
|
|
147
|
+
...(remaining !== undefined && { remaining }),
|
|
148
|
+
...(requestResetAt && { resetAt: requestResetAt }),
|
|
149
|
+
...(retryAfter !== undefined && { retryAfterSeconds: retryAfter }),
|
|
150
|
+
...(tokenLimit !== undefined && { tokenLimit }),
|
|
151
|
+
...(tokenRemaining !== undefined && { tokenRemaining }),
|
|
152
|
+
...(tokenResetAt && { tokenResetAt }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Converts provider headers to the deliberately small telemetry-safe surface.
|
|
158
|
+
* This function never returns request bodies, credentials, cookies, or arbitrary
|
|
159
|
+
* provider headers.
|
|
160
|
+
*/
|
|
161
|
+
export function normalizeResponseMetadata(input: {
|
|
162
|
+
statusCode?: number;
|
|
163
|
+
headers?: HeaderSource;
|
|
164
|
+
requestId?: string | null;
|
|
165
|
+
observedAt?: Date;
|
|
166
|
+
}): ProviderResponseMetadata {
|
|
167
|
+
const headers: Record<string, string> = {};
|
|
168
|
+
let totalChars = 0;
|
|
169
|
+
|
|
170
|
+
for (const [rawName, rawValue] of entriesFor(input.headers)) {
|
|
171
|
+
const name = String(rawName).toLowerCase();
|
|
172
|
+
if (
|
|
173
|
+
!isSafeHeader(name) ||
|
|
174
|
+
Object.prototype.hasOwnProperty.call(headers, name)
|
|
175
|
+
)
|
|
176
|
+
continue;
|
|
177
|
+
const value = String(rawValue).slice(0, MAX_HEADER_VALUE_LENGTH);
|
|
178
|
+
if (
|
|
179
|
+
Object.keys(headers).length >= MAX_HEADERS ||
|
|
180
|
+
totalChars + name.length + value.length > MAX_TOTAL_HEADER_CHARS
|
|
181
|
+
)
|
|
182
|
+
break;
|
|
183
|
+
headers[name] = value;
|
|
184
|
+
totalChars += name.length + value.length;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const requestId =
|
|
188
|
+
input.requestId ??
|
|
189
|
+
headers["request-id"] ??
|
|
190
|
+
headers["x-request-id"] ??
|
|
191
|
+
headers["x-amzn-requestid"] ??
|
|
192
|
+
headers["x-amz-request-id"];
|
|
193
|
+
return {
|
|
194
|
+
...(typeof input.statusCode === "number" && {
|
|
195
|
+
statusCode: input.statusCode,
|
|
196
|
+
}),
|
|
197
|
+
rateLimit: normalizeRateLimitMetadata(headers, input.observedAt),
|
|
198
|
+
...(Object.keys(headers).length > 0 && { headers }),
|
|
199
|
+
...(requestId && {
|
|
200
|
+
requestId: String(requestId).slice(0, MAX_HEADER_VALUE_LENGTH),
|
|
201
|
+
}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Emits one visible HTTP response synchronously and isolates observer failures. */
|
|
206
|
+
export function emitResponseMetadata(
|
|
207
|
+
options: Pick<RetryOptions, "onResponseMetadata"> | undefined,
|
|
208
|
+
input: {
|
|
209
|
+
statusCode?: number;
|
|
210
|
+
headers?: HeaderSource;
|
|
211
|
+
requestId?: string | null;
|
|
212
|
+
}
|
|
213
|
+
) {
|
|
214
|
+
const observer = options?.onResponseMetadata;
|
|
215
|
+
if (!observer) return;
|
|
216
|
+
try {
|
|
217
|
+
observer(normalizeResponseMetadata(input));
|
|
218
|
+
} catch {
|
|
219
|
+
// Observability must never affect a provider request or its original error.
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/clients/types.ts
CHANGED
|
@@ -104,6 +104,36 @@ export interface Tool {
|
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Provider-neutral rate-limit information derived from response headers.
|
|
109
|
+
* Reset values are ISO-8601 strings so callback payloads remain JSON-safe.
|
|
110
|
+
*/
|
|
111
|
+
export interface ProviderRateLimitMetadata {
|
|
112
|
+
limit?: number;
|
|
113
|
+
remaining?: number;
|
|
114
|
+
resetAt?: string;
|
|
115
|
+
retryAfterSeconds?: number;
|
|
116
|
+
tokenLimit?: number;
|
|
117
|
+
tokenRemaining?: number;
|
|
118
|
+
tokenResetAt?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Safe telemetry details for one provider response. `rateLimit` is normalized
|
|
123
|
+
* by the client for every observed response. `headers`, when present, contains
|
|
124
|
+
* only the bounded allowlisted raw headers used to derive it.
|
|
125
|
+
*/
|
|
126
|
+
export interface ProviderResponseMetadata {
|
|
127
|
+
statusCode?: number;
|
|
128
|
+
rateLimit: ProviderRateLimitMetadata;
|
|
129
|
+
headers?: Record<string, string>;
|
|
130
|
+
requestId?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type ProviderResponseMetadataObserver = (
|
|
134
|
+
metadata: ProviderResponseMetadata
|
|
135
|
+
) => void;
|
|
136
|
+
|
|
107
137
|
export interface ToolCall {
|
|
108
138
|
id: string;
|
|
109
139
|
type: "function";
|
|
@@ -134,6 +164,11 @@ export interface RetryOptions {
|
|
|
134
164
|
* attempt is cancelled immediately and no further retries are made.
|
|
135
165
|
*/
|
|
136
166
|
signal?: AbortSignal;
|
|
167
|
+
/**
|
|
168
|
+
* Synchronous observer invoked once for each provider HTTP response visible to
|
|
169
|
+
* this client, including errors and streaming handshakes.
|
|
170
|
+
*/
|
|
171
|
+
onResponseMetadata?: ProviderResponseMetadataObserver;
|
|
137
172
|
}
|
|
138
173
|
|
|
139
174
|
/**
|
|
@@ -150,10 +185,13 @@ export const REASONING_EFFORTS = [
|
|
|
150
185
|
"xhigh",
|
|
151
186
|
"max",
|
|
152
187
|
] as const;
|
|
153
|
-
export type ReasoningEffort =
|
|
188
|
+
export type ReasoningEffort = typeof REASONING_EFFORTS[number];
|
|
154
189
|
|
|
155
190
|
export function isReasoningEffort(value: unknown): value is ReasoningEffort {
|
|
156
|
-
return
|
|
191
|
+
return (
|
|
192
|
+
typeof value === "string" &&
|
|
193
|
+
REASONING_EFFORTS.includes(value as ReasoningEffort)
|
|
194
|
+
);
|
|
157
195
|
}
|
|
158
196
|
|
|
159
197
|
export interface CompletionOptions extends RetryOptions {
|
|
@@ -438,7 +476,9 @@ export interface GenericClient {
|
|
|
438
476
|
setKey(key: string): void;
|
|
439
477
|
createChatCompletion(options: CompletionOptions): Promise<CompletionResponse>;
|
|
440
478
|
/** Optional streaming variant — yields incremental tokens then a final done chunk. */
|
|
441
|
-
createChatCompletionStream?(
|
|
479
|
+
createChatCompletionStream?(
|
|
480
|
+
options: CompletionOptions
|
|
481
|
+
): AsyncGenerator<StreamChunk>;
|
|
442
482
|
createEmbedding(options: EmbeddingOptions): Promise<EmbeddingResponse>;
|
|
443
483
|
createAudioTranscription?(
|
|
444
484
|
options: AudioTranscriptionOptions
|
|
@@ -482,5 +522,10 @@ export interface GenericClient {
|
|
|
482
522
|
* Returns undefined for a specific model if no pricing is known.
|
|
483
523
|
* Only implemented by HttpClient-based providers that have been given a pricing map via setPrices().
|
|
484
524
|
*/
|
|
485
|
-
getPricing?(
|
|
525
|
+
getPricing?(
|
|
526
|
+
model?: string
|
|
527
|
+
):
|
|
528
|
+
| import("./pricing/types").ModelPricing
|
|
529
|
+
| Record<string, import("./pricing/types").ModelPricing>
|
|
530
|
+
| undefined;
|
|
486
531
|
}
|
package/src/commands/mcp.ts
CHANGED
package/src/commands/misc.ts
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getConfig, updateConfig } from "../config";
|
|
3
|
+
import {
|
|
4
|
+
createRemote,
|
|
5
|
+
DEFAULT_API_URL,
|
|
6
|
+
DEFAULT_REMOTE_NAME,
|
|
7
|
+
getConfiguredRemotes,
|
|
8
|
+
registerRemote,
|
|
9
|
+
} from "../remotes";
|
|
10
|
+
|
|
11
|
+
export function addRemoteCommand(program: Command): void {
|
|
12
|
+
const command = program
|
|
13
|
+
.command("remote")
|
|
14
|
+
.description("Manage Knowhow API remotes");
|
|
15
|
+
|
|
16
|
+
command
|
|
17
|
+
.command("add")
|
|
18
|
+
.description("Register a Knowhow-compatible backend")
|
|
19
|
+
.argument("<name>", "Remote name")
|
|
20
|
+
.argument("<api-url>", "API base URL")
|
|
21
|
+
.action(async (name: string, apiUrl: string) => {
|
|
22
|
+
const config = await getConfig();
|
|
23
|
+
if (config.remotes?.[name]) {
|
|
24
|
+
throw new Error(`Remote '${name}' already exists.`);
|
|
25
|
+
}
|
|
26
|
+
const remote = createRemote(name, apiUrl);
|
|
27
|
+
registerRemote(config, remote);
|
|
28
|
+
await updateConfig(config);
|
|
29
|
+
console.log(`Added remote '${name}' (${remote.apiUrl}).`);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
command
|
|
33
|
+
.command("use")
|
|
34
|
+
.description("Select the remote used by Knowhow commands in this configuration")
|
|
35
|
+
.argument("<name>", "Remote name")
|
|
36
|
+
.action(async (name: string) => {
|
|
37
|
+
const config = await getConfig();
|
|
38
|
+
const remote = getConfiguredRemotes(config)[name] ??
|
|
39
|
+
(name === DEFAULT_REMOTE_NAME ? createRemote(DEFAULT_REMOTE_NAME, DEFAULT_API_URL) : undefined);
|
|
40
|
+
if (!remote) {
|
|
41
|
+
throw new Error(`Unknown remote '${name}'. Add it with: knowhow remote add ${name} <api-url>`);
|
|
42
|
+
}
|
|
43
|
+
registerRemote(config, remote);
|
|
44
|
+
config.activeRemote = name;
|
|
45
|
+
await updateConfig(config);
|
|
46
|
+
console.log(`Using remote '${name}' (${remote.apiUrl}).`);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
command
|
|
50
|
+
.command("list")
|
|
51
|
+
.alias("ls")
|
|
52
|
+
.description("List configured remotes")
|
|
53
|
+
.action(async () => {
|
|
54
|
+
const config = await getConfig();
|
|
55
|
+
const remotes = { ...getConfiguredRemotes(config) };
|
|
56
|
+
if (!remotes[DEFAULT_REMOTE_NAME]) {
|
|
57
|
+
remotes[DEFAULT_REMOTE_NAME] = createRemote(DEFAULT_REMOTE_NAME, DEFAULT_API_URL);
|
|
58
|
+
}
|
|
59
|
+
for (const [name, remote] of Object.entries(remotes)) {
|
|
60
|
+
const marker = (config.activeRemote ?? DEFAULT_REMOTE_NAME) === name ? "*" : " ";
|
|
61
|
+
console.log(`${marker} ${name}\t${remote.apiUrl}\t${remote.jwtPath}${remote.orgId ? `\t${remote.orgId}` : ""}`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
command
|
|
66
|
+
.command("get-url")
|
|
67
|
+
.description("Print a remote API URL")
|
|
68
|
+
.argument("[name]", "Remote name")
|
|
69
|
+
.action(async (name?: string) => {
|
|
70
|
+
const config = await getConfig();
|
|
71
|
+
const selectedName = name ?? config.activeRemote ?? DEFAULT_REMOTE_NAME;
|
|
72
|
+
const remote = getConfiguredRemotes(config)[selectedName] ??
|
|
73
|
+
(selectedName === DEFAULT_REMOTE_NAME ? createRemote(DEFAULT_REMOTE_NAME, DEFAULT_API_URL) : undefined);
|
|
74
|
+
if (!remote) throw new Error(`Unknown remote '${selectedName}'.`);
|
|
75
|
+
console.log(remote.apiUrl);
|
|
76
|
+
});
|
|
77
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -4,7 +4,6 @@ import * as os from "os";
|
|
|
4
4
|
import gitignoreToGlob from "gitignore-to-glob";
|
|
5
5
|
import { Prompts } from "./prompts";
|
|
6
6
|
import { promisify } from "util";
|
|
7
|
-
import { KNOWHOW_API_URL } from "./services/KnowhowClient";
|
|
8
7
|
import {
|
|
9
8
|
Config,
|
|
10
9
|
SourceRef,
|
|
@@ -113,6 +112,15 @@ const defaultConfig = {
|
|
|
113
112
|
},
|
|
114
113
|
},
|
|
115
114
|
|
|
115
|
+
activeRemote: "origin",
|
|
116
|
+
remotes: {
|
|
117
|
+
origin: {
|
|
118
|
+
name: "origin",
|
|
119
|
+
apiUrl: "https://api.knowhow.tyvm.ai",
|
|
120
|
+
jwtPath: ".knowhow/.jwt",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
|
|
116
124
|
syncRemote: false,
|
|
117
125
|
} as Config;
|
|
118
126
|
|