modelrig 0.0.1 → 0.1.0
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/LICENSE +201 -0
- package/README.md +115 -8
- package/dist/adapters/anthropic.d.ts +46 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +202 -0
- package/dist/adapters/deepseek.d.ts +41 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +163 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +222 -0
- package/dist/adapters/grok.d.ts +44 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +168 -0
- package/dist/adapters/openai-compat.d.ts +74 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +187 -0
- package/dist/adapters/openai.d.ts +49 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +222 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/samples.d.ts +62 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +128 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +27 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +572 -0
- package/dist/config.d.ts +104 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +148 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/index.d.ts +155 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +448 -0
- package/dist/init/finder.d.ts +79 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +297 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +126 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +192 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +72 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +120 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +26 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +41 -0
- package/dist/registry/pricing.d.ts +19 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +69 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +26 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +436 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +26 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +81 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +41 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +0 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +56 -0
- package/dist/savings/emit.d.ts +25 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +75 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +44 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +87 -0
- package/dist/savings/swaps-client.d.ts +41 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +81 -0
- package/dist/telemetry/capture.d.ts +28 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +74 -0
- package/dist/telemetry/exporter.d.ts +62 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +233 -0
- package/dist/telemetry/ingest-protocol.d.ts +161 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +163 -0
- package/dist/telemetry/sinks.d.ts +54 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +89 -0
- package/dist/telemetry/store.d.ts +38 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +332 -0
- package/dist/telemetry/supabase-exporter.d.ts +64 -0
- package/dist/telemetry/supabase-exporter.d.ts.map +1 -0
- package/dist/telemetry/supabase-exporter.js +211 -0
- package/dist/tenancy/api-key-format.d.ts +67 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +91 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +79 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +20 -0
- package/dist/types/route.d.ts +127 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +89 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +66 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +94 -0
- package/dist/watch/proposals.d.ts +73 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +169 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +40 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +44 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +50 -14
- package/index.js +0 -3
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DeepSeek ProviderAdapter — plan step 5 (sanctioned cut if C1 date at risk).
|
|
4
|
+
* Transformation logic ported from apps/api/src/services/deepseek-provider.ts:
|
|
5
|
+
* OpenAI-compatible API at api.deepseek.com, json_object forcing (DeepSeek has
|
|
6
|
+
* NO native strict json_schema — the run loop's json_mode path injects the
|
|
7
|
+
* schema-coached prompt; this adapter receives it already coached),
|
|
8
|
+
* prompt_cache_hit_tokens accounting, reasoning_content excluded from the
|
|
9
|
+
* JSON buffer (only delta.content is accumulated).
|
|
10
|
+
* NOT ported: logging, pricing tables, env reads, the analyst-persona prompt.
|
|
11
|
+
*
|
|
12
|
+
* Portions may be derived from LiteLLM (https://github.com/BerriAI/litellm),
|
|
13
|
+
* Copyright (c) 2023 Berri AI, MIT License — retain this notice if LiteLLM's
|
|
14
|
+
* deepseek transformation logic is consulted during the port.
|
|
15
|
+
*/
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.deepseekSupports = deepseekSupports;
|
|
21
|
+
exports.classifyDeepSeekFailure = classifyDeepSeekFailure;
|
|
22
|
+
exports.buildDeepSeekParams = buildDeepSeekParams;
|
|
23
|
+
exports.createDeepSeekAdapter = createDeepSeekAdapter;
|
|
24
|
+
const openai_1 = __importDefault(require("openai"));
|
|
25
|
+
const DEEPSEEK_BASE_URL = "https://api.deepseek.com";
|
|
26
|
+
const DEFAULT_MAX_TOKENS = 64000; // R1/V3 max — full capacity for long reports (ported)
|
|
27
|
+
function deepseekSupports(feature, model) {
|
|
28
|
+
if (feature === "trace_visible") {
|
|
29
|
+
// Reasoner models stream reasoning_content — the trace IS caller-visible.
|
|
30
|
+
return model.includes("reasoner");
|
|
31
|
+
}
|
|
32
|
+
return feature === "json_mode" || feature === "prefix_cache_implicit";
|
|
33
|
+
}
|
|
34
|
+
function classifyDeepSeekFailure(message) {
|
|
35
|
+
const msg = message.toLowerCase();
|
|
36
|
+
if (/\b429\b|rate limit|\brate\b/.test(msg))
|
|
37
|
+
return { cls: "capacity_shed" };
|
|
38
|
+
if (/\b50[023]\b|service unavailable|overloaded/.test(msg))
|
|
39
|
+
return { cls: "capacity_shed" };
|
|
40
|
+
if (/timeout|etimedout|abort/.test(msg))
|
|
41
|
+
return { cls: "timeout" };
|
|
42
|
+
if (/econnreset|econnrefused|enotfound|fetch failed|network/.test(msg))
|
|
43
|
+
return { cls: "network" };
|
|
44
|
+
if (/\b401\b|api key/.test(msg)) {
|
|
45
|
+
// F2: non-retryable — a dead key never burns backoff retries.
|
|
46
|
+
return { cls: "config_auth", fixHint: "check the DeepSeek API key configured for this rig" };
|
|
47
|
+
}
|
|
48
|
+
if (/not found|does not exist/.test(msg)) {
|
|
49
|
+
return {
|
|
50
|
+
cls: "network",
|
|
51
|
+
fixHint: "the candidate model was not found — fix the model id in the route's candidate list",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { cls: "network" };
|
|
55
|
+
}
|
|
56
|
+
/** DeepSeek's json_object mode 400s when the literal word "JSON" appears
|
|
57
|
+
* nowhere in the prompt. The run loop's coached prompt normally guarantees
|
|
58
|
+
* it; this one-line guard is the adapter's belt-and-suspenders (ported). */
|
|
59
|
+
const JSON_GUARD = "Respond with a single valid JSON object.";
|
|
60
|
+
function buildDeepSeekParams(model, req) {
|
|
61
|
+
const wantJson = req.outputSchema !== null;
|
|
62
|
+
let systemPrompt = req.systemPrompt;
|
|
63
|
+
if (wantJson && !req.systemPrompt.includes("JSON") && !req.userPrompt.includes("JSON")) {
|
|
64
|
+
systemPrompt = systemPrompt === "" ? JSON_GUARD : `${systemPrompt}\n\n${JSON_GUARD}`;
|
|
65
|
+
}
|
|
66
|
+
const messages = [
|
|
67
|
+
...(systemPrompt !== "" ? [{ role: "system", content: systemPrompt }] : []),
|
|
68
|
+
{ role: "user", content: req.userPrompt },
|
|
69
|
+
];
|
|
70
|
+
return {
|
|
71
|
+
model,
|
|
72
|
+
max_tokens: req.maxOutputTokens ?? DEFAULT_MAX_TOKENS,
|
|
73
|
+
messages,
|
|
74
|
+
stream: true,
|
|
75
|
+
stream_options: { include_usage: true },
|
|
76
|
+
...(wantJson ? { response_format: { type: "json_object" } } : {}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function createDeepSeekAdapter(config) {
|
|
80
|
+
return {
|
|
81
|
+
id: "deepseek",
|
|
82
|
+
supports: deepseekSupports,
|
|
83
|
+
async call(req) {
|
|
84
|
+
const model = req.candidate.model;
|
|
85
|
+
// F1: post-stream failures still billed tokens — attach usage.
|
|
86
|
+
const fail = (cls, message, fixHint, usage) => ({
|
|
87
|
+
ok: false,
|
|
88
|
+
failure: {
|
|
89
|
+
class: cls,
|
|
90
|
+
message,
|
|
91
|
+
provider: "deepseek",
|
|
92
|
+
model,
|
|
93
|
+
...(fixHint !== undefined ? { fixHint } : {}),
|
|
94
|
+
},
|
|
95
|
+
...(usage !== undefined ? { usage } : {}),
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
const client = new openai_1.default({
|
|
99
|
+
apiKey: config.apiKey,
|
|
100
|
+
baseURL: DEEPSEEK_BASE_URL,
|
|
101
|
+
timeout: req.timeoutMs,
|
|
102
|
+
});
|
|
103
|
+
const params = buildDeepSeekParams(model, req);
|
|
104
|
+
const dispatchedAt = Date.now();
|
|
105
|
+
const stream = await client.chat.completions.create(params);
|
|
106
|
+
let text = "";
|
|
107
|
+
let promptTokens = 0;
|
|
108
|
+
let completionTokens = 0;
|
|
109
|
+
let cachedTokens = 0;
|
|
110
|
+
let finishReason = null;
|
|
111
|
+
// TTFB (Phase 4 WS3): first streamed chunk = first body byte.
|
|
112
|
+
let ttfbMs;
|
|
113
|
+
// Only delta.content is accumulated — a reasoner's reasoning_content
|
|
114
|
+
// (chain-of-thought) must never leak into the JSON buffer (ported).
|
|
115
|
+
for await (const chunk of stream) {
|
|
116
|
+
if (ttfbMs === undefined)
|
|
117
|
+
ttfbMs = Date.now() - dispatchedAt;
|
|
118
|
+
const choice = chunk.choices[0];
|
|
119
|
+
if (choice?.delta?.content)
|
|
120
|
+
text += choice.delta.content;
|
|
121
|
+
if (choice?.finish_reason)
|
|
122
|
+
finishReason = choice.finish_reason;
|
|
123
|
+
if (chunk.usage) {
|
|
124
|
+
promptTokens = chunk.usage.prompt_tokens ?? 0;
|
|
125
|
+
completionTokens = chunk.usage.completion_tokens ?? 0;
|
|
126
|
+
// DeepSeek's automatic prefix cache: tokens billed ~10x cheaper.
|
|
127
|
+
cachedTokens = chunk.usage.prompt_cache_hit_tokens ?? 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// DeepSeek has no service tiers — served is always standard.
|
|
131
|
+
const usage = {
|
|
132
|
+
tokensIn: promptTokens,
|
|
133
|
+
tokensOut: completionTokens,
|
|
134
|
+
tokensCached: cachedTokens,
|
|
135
|
+
servedTier: "standard",
|
|
136
|
+
...(ttfbMs !== undefined ? { ttfbMs } : {}),
|
|
137
|
+
};
|
|
138
|
+
if (text.trim().length === 0) {
|
|
139
|
+
return fail("content_invalid", `empty response from DeepSeek (finish_reason=${finishReason ?? "none"})`, undefined, usage);
|
|
140
|
+
}
|
|
141
|
+
if (finishReason === "length" && req.outputSchema !== null) {
|
|
142
|
+
return fail("content_invalid", "DeepSeek JSON output truncated (finish_reason=length)", "raise maxOutputTokens for this route", usage);
|
|
143
|
+
}
|
|
144
|
+
return { ok: true, text, usage };
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
const { cls, fixHint } = classifyDeepSeekFailure(message);
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
failure: {
|
|
152
|
+
class: cls,
|
|
153
|
+
message,
|
|
154
|
+
provider: "deepseek",
|
|
155
|
+
model,
|
|
156
|
+
cause: error,
|
|
157
|
+
...(fixHint !== undefined ? { fixHint } : {}),
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini ProviderAdapter — plan step 5 (the hardest port).
|
|
3
|
+
* Transformation logic ported from apps/api/src/services/gemini-provider.ts:
|
|
4
|
+
* grounding tool passthrough, explicit context-cache passthrough (cache name
|
|
5
|
+
* via AdapterRequest.cache.key), thinking level, flex tier + capacity-shed
|
|
6
|
+
* classification, responseJsonSchema, served-tier extraction.
|
|
7
|
+
* NOT ported (by design): logging, pricing tables (registry owns pricing),
|
|
8
|
+
* env reads, the analyst-persona system instruction (route templates own
|
|
9
|
+
* prompt content), and especially the model fallback chain — silent model
|
|
10
|
+
* substitution is an invariant violation here; a failed model fails typed.
|
|
11
|
+
*
|
|
12
|
+
* Portions may be derived from LiteLLM (https://github.com/BerriAI/litellm),
|
|
13
|
+
* Copyright (c) 2023 Berri AI, MIT License — retain this notice if LiteLLM's
|
|
14
|
+
* gemini transformation logic is consulted during the port.
|
|
15
|
+
*/
|
|
16
|
+
import type { AdapterRequest, CapabilityFlag, ProviderAdapter } from "../types/provider";
|
|
17
|
+
import type { FailureClass } from "../types/failures";
|
|
18
|
+
export interface GeminiAdapterConfig {
|
|
19
|
+
readonly apiKey: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function geminiSupports(feature: CapabilityFlag, model: string): boolean;
|
|
22
|
+
export interface ClassifiedFailure {
|
|
23
|
+
readonly cls: FailureClass;
|
|
24
|
+
readonly fixHint?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Map a Gemini SDK/API error message into the failure taxonomy. Ported from
|
|
28
|
+
* classifyGeminiError + classifyGeminiFallbackReason; branch ORDER is
|
|
29
|
+
* load-bearing — auth before INVALID_ARGUMENT (a bad key surfaces as 400
|
|
30
|
+
* INVALID_ARGUMENT "API key not valid"), quota before generic 429 capacity.
|
|
31
|
+
*/
|
|
32
|
+
export declare function classifyGeminiFailure(message: string): ClassifiedFailure;
|
|
33
|
+
/** The candidate-independent part of an AdapterRequest (also what the pure
|
|
34
|
+
* shaping helpers take, so unit tests never need a branded CandidateRef). */
|
|
35
|
+
export type ShapingInput = Omit<AdapterRequest, "candidate">;
|
|
36
|
+
export interface GeminiShapedRequest {
|
|
37
|
+
readonly model: string;
|
|
38
|
+
readonly contents: string;
|
|
39
|
+
readonly config: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** Pure request shaping — everything except the abort signal. */
|
|
42
|
+
export declare function buildGeminiRequest(model: string, req: ShapingInput): GeminiShapedRequest;
|
|
43
|
+
export declare function createGeminiAdapter(config: GeminiAdapterConfig): ProviderAdapter;
|
|
44
|
+
//# sourceMappingURL=gemini.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/adapters/gemini.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EACV,cAAc,EAGd,cAAc,EACd,eAAe,EAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAyBD,wBAAgB,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAG9E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAyCxE;AAED;6EAC6E;AAC7E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC1C;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,mBAAmB,CAkCxF;AA0BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,eAAe,CAyHhF"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gemini ProviderAdapter — plan step 5 (the hardest port).
|
|
4
|
+
* Transformation logic ported from apps/api/src/services/gemini-provider.ts:
|
|
5
|
+
* grounding tool passthrough, explicit context-cache passthrough (cache name
|
|
6
|
+
* via AdapterRequest.cache.key), thinking level, flex tier + capacity-shed
|
|
7
|
+
* classification, responseJsonSchema, served-tier extraction.
|
|
8
|
+
* NOT ported (by design): logging, pricing tables (registry owns pricing),
|
|
9
|
+
* env reads, the analyst-persona system instruction (route templates own
|
|
10
|
+
* prompt content), and especially the model fallback chain — silent model
|
|
11
|
+
* substitution is an invariant violation here; a failed model fails typed.
|
|
12
|
+
*
|
|
13
|
+
* Portions may be derived from LiteLLM (https://github.com/BerriAI/litellm),
|
|
14
|
+
* Copyright (c) 2023 Berri AI, MIT License — retain this notice if LiteLLM's
|
|
15
|
+
* gemini transformation logic is consulted during the port.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.geminiSupports = geminiSupports;
|
|
19
|
+
exports.classifyGeminiFailure = classifyGeminiFailure;
|
|
20
|
+
exports.buildGeminiRequest = buildGeminiRequest;
|
|
21
|
+
exports.createGeminiAdapter = createGeminiAdapter;
|
|
22
|
+
const genai_1 = require("@google/genai");
|
|
23
|
+
/** Models that accept thinkingConfig — passing it to 2.x models 400s.
|
|
24
|
+
* Ported from the production THINKING_CAPABLE_MODELS set. */
|
|
25
|
+
const THINKING_CAPABLE_MODELS = new Set([
|
|
26
|
+
"gemini-3.1-pro-preview",
|
|
27
|
+
"gemini-3.0-pro-preview",
|
|
28
|
+
"gemini-3.1-flash-preview",
|
|
29
|
+
]);
|
|
30
|
+
/** Gemini 3 requires temperature=1.0 — lower values cause looping (ported). */
|
|
31
|
+
const GEMINI_TEMPERATURE = 1.0;
|
|
32
|
+
const DEFAULT_MAX_OUTPUT_TOKENS = 64000; // thinking tokens count against this
|
|
33
|
+
const TOP_P = 0.95;
|
|
34
|
+
const TOP_K = 40;
|
|
35
|
+
const GEMINI_CAPABILITIES = new Set([
|
|
36
|
+
"structured_native",
|
|
37
|
+
"json_mode",
|
|
38
|
+
"grounded_native",
|
|
39
|
+
"context_cache_explicit",
|
|
40
|
+
"prefix_cache_implicit",
|
|
41
|
+
"tier_flex",
|
|
42
|
+
]);
|
|
43
|
+
function geminiSupports(feature, model) {
|
|
44
|
+
void model; // Phase 1 static stub: flags are family-wide for the 2.5/3.x models in use
|
|
45
|
+
return GEMINI_CAPABILITIES.has(feature);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Map a Gemini SDK/API error message into the failure taxonomy. Ported from
|
|
49
|
+
* classifyGeminiError + classifyGeminiFallbackReason; branch ORDER is
|
|
50
|
+
* load-bearing — auth before INVALID_ARGUMENT (a bad key surfaces as 400
|
|
51
|
+
* INVALID_ARGUMENT "API key not valid"), quota before generic 429 capacity.
|
|
52
|
+
*/
|
|
53
|
+
function classifyGeminiFailure(message) {
|
|
54
|
+
const msg = message.toLowerCase();
|
|
55
|
+
if (/cachedcontent|cached_content/.test(msg)) {
|
|
56
|
+
return { cls: "cache_invalid", fixHint: "recreate the context cache and retry without it if it keeps failing" };
|
|
57
|
+
}
|
|
58
|
+
if (/\b40[13]\b|api[_ ]key|permission_denied|unauthenticated/.test(msg)) {
|
|
59
|
+
// F2: a dead key must not burn backoff retries — config_auth is
|
|
60
|
+
// non-retryable and the run loop advances to the next candidate.
|
|
61
|
+
return { cls: "config_auth", fixHint: "check the Gemini API key/credentials configured for this rig" };
|
|
62
|
+
}
|
|
63
|
+
if (/quota|\brpm\b|\btpm\b|rate limit/.test(msg)) {
|
|
64
|
+
return { cls: "capacity_shed" };
|
|
65
|
+
}
|
|
66
|
+
if (/\b50[234]\b/.test(msg) ||
|
|
67
|
+
/service unavailable|preempted|sheddable|overloaded|bad gateway|gateway timeout/.test(msg) ||
|
|
68
|
+
/\b429\b|resource has been exhausted|serving capacity/.test(msg)) {
|
|
69
|
+
return { cls: "capacity_shed" };
|
|
70
|
+
}
|
|
71
|
+
if (/abort|cancel|timeout|etimedout/.test(msg)) {
|
|
72
|
+
return { cls: "timeout" };
|
|
73
|
+
}
|
|
74
|
+
if (/fetch failed|econnreset|econnrefused|enotfound|eai_again|epipe|eproto|err_tls/.test(msg)) {
|
|
75
|
+
return { cls: "network" };
|
|
76
|
+
}
|
|
77
|
+
if (/\b404\b|not found/.test(msg)) {
|
|
78
|
+
return {
|
|
79
|
+
cls: "network",
|
|
80
|
+
fixHint: "the candidate model was not found — fix the model id in the route's candidate list",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (/invalid_argument|invalid argument/.test(msg)) {
|
|
84
|
+
return {
|
|
85
|
+
cls: "network",
|
|
86
|
+
fixHint: "request shape rejected (often a response schema over Gemini's complexity ceiling) — another candidate may accept it",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { cls: "network" };
|
|
90
|
+
}
|
|
91
|
+
/** Pure request shaping — everything except the abort signal. */
|
|
92
|
+
function buildGeminiRequest(model, req) {
|
|
93
|
+
const usingCache = req.cache?.key !== undefined && req.cache.key !== "";
|
|
94
|
+
const config = {
|
|
95
|
+
temperature: GEMINI_TEMPERATURE,
|
|
96
|
+
maxOutputTokens: req.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
|
|
97
|
+
topP: TOP_P,
|
|
98
|
+
topK: TOP_K,
|
|
99
|
+
...(THINKING_CAPABLE_MODELS.has(model) ? { thinkingConfig: { thinkingLevel: "high" } } : {}),
|
|
100
|
+
};
|
|
101
|
+
if (req.outputSchema !== null) {
|
|
102
|
+
config.responseMimeType = "application/json";
|
|
103
|
+
config.responseJsonSchema = req.outputSchema;
|
|
104
|
+
}
|
|
105
|
+
// cachedContent excludes systemInstruction and tools — the API rejects
|
|
106
|
+
// requests setting both (they are baked into the cache at creation time,
|
|
107
|
+
// including the googleSearch tool for cached grounded steps).
|
|
108
|
+
if (usingCache) {
|
|
109
|
+
config.cachedContent = req.cache?.key;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
if (req.systemPrompt !== "") {
|
|
113
|
+
config.systemInstruction = req.systemPrompt;
|
|
114
|
+
}
|
|
115
|
+
if (req.grounding === true) {
|
|
116
|
+
config.tools = [{ googleSearch: {} }];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (req.requestedTier === "flex") {
|
|
120
|
+
config.serviceTier = "flex";
|
|
121
|
+
}
|
|
122
|
+
return { model, contents: req.userPrompt, config };
|
|
123
|
+
}
|
|
124
|
+
/** Best-effort served-tier extraction (ported): response.serviceTier, then
|
|
125
|
+
* usageMetadata.trafficType (Vertex path), else assume the requested tier —
|
|
126
|
+
* production bills flex when a flex request was accepted without a tier echo. */
|
|
127
|
+
function extractServedTier(rawServiceTier, trafficType, requested) {
|
|
128
|
+
if (rawServiceTier === "flex" || trafficType === "ON_DEMAND_FLEX")
|
|
129
|
+
return "flex";
|
|
130
|
+
if (rawServiceTier === "standard" || rawServiceTier === "default" || trafficType === "ON_DEMAND") {
|
|
131
|
+
return "standard";
|
|
132
|
+
}
|
|
133
|
+
if (rawServiceTier === "priority" || trafficType === "ON_DEMAND_PRIORITY")
|
|
134
|
+
return "priority";
|
|
135
|
+
return requested;
|
|
136
|
+
}
|
|
137
|
+
function createGeminiAdapter(config) {
|
|
138
|
+
const ai = new genai_1.GoogleGenAI({ apiKey: config.apiKey });
|
|
139
|
+
return {
|
|
140
|
+
id: "gemini",
|
|
141
|
+
supports: geminiSupports,
|
|
142
|
+
async call(req) {
|
|
143
|
+
const model = req.candidate.model;
|
|
144
|
+
const abortController = new AbortController();
|
|
145
|
+
const timeoutId = setTimeout(() => abortController.abort(), req.timeoutMs);
|
|
146
|
+
// F1: post-response failures (refusal/empty/truncation) still billed
|
|
147
|
+
// tokens — they attach the response usage so the run loop accounts them.
|
|
148
|
+
const fail = (cls, message, fixHint, usage) => ({
|
|
149
|
+
ok: false,
|
|
150
|
+
failure: {
|
|
151
|
+
class: cls,
|
|
152
|
+
message,
|
|
153
|
+
provider: "gemini",
|
|
154
|
+
model,
|
|
155
|
+
...(fixHint !== undefined ? { fixHint } : {}),
|
|
156
|
+
},
|
|
157
|
+
...(usage !== undefined ? { usage } : {}),
|
|
158
|
+
});
|
|
159
|
+
try {
|
|
160
|
+
const shaped = buildGeminiRequest(model, req);
|
|
161
|
+
const dispatchedAt = Date.now();
|
|
162
|
+
const response = await ai.models.generateContent({
|
|
163
|
+
model: shaped.model,
|
|
164
|
+
contents: shaped.contents,
|
|
165
|
+
config: { ...shaped.config, abortSignal: abortController.signal },
|
|
166
|
+
});
|
|
167
|
+
// TTFB (Phase 4 WS3): generateContent is non-streaming — the first
|
|
168
|
+
// body byte genuinely arrives with the whole body, so TTFB = total.
|
|
169
|
+
const ttfbMs = Date.now() - dispatchedAt;
|
|
170
|
+
const usageMetadata = (response.usageMetadata ?? {});
|
|
171
|
+
const respWithTier = response;
|
|
172
|
+
const servedTier = extractServedTier(respWithTier.serviceTier, usageMetadata.trafficType, req.requestedTier);
|
|
173
|
+
const tokensIn = usageMetadata.promptTokenCount ?? 0;
|
|
174
|
+
// Thinking tokens are billed as output and count against
|
|
175
|
+
// maxOutputTokens — include them in tokensOut for honest accounting.
|
|
176
|
+
const tokensOut = (usageMetadata.candidatesTokenCount ?? 0) + (usageMetadata.thoughtsTokenCount ?? 0);
|
|
177
|
+
const tokensCached = usageMetadata.cachedContentTokenCount ?? 0;
|
|
178
|
+
const usage = { tokensIn, tokensOut, tokensCached, servedTier, ttfbMs };
|
|
179
|
+
const candidates = response
|
|
180
|
+
.candidates;
|
|
181
|
+
const finishReason = candidates?.[0]?.finishReason;
|
|
182
|
+
if (finishReason === "SAFETY" || finishReason === "PROHIBITED_CONTENT") {
|
|
183
|
+
return fail("refusal", `Gemini blocked output via safety filter (finishReason=${finishReason})`, undefined, usage);
|
|
184
|
+
}
|
|
185
|
+
if (finishReason === "RECITATION") {
|
|
186
|
+
return fail("refusal", "Gemini blocked output via recitation filter (finishReason=RECITATION)", undefined, usage);
|
|
187
|
+
}
|
|
188
|
+
const text = response.text ?? "";
|
|
189
|
+
if (text.trim().length === 0) {
|
|
190
|
+
return fail("content_invalid", `empty response from Gemini (finishReason=${finishReason ?? "none"})`, undefined, usage);
|
|
191
|
+
}
|
|
192
|
+
if (finishReason === "MAX_TOKENS" && req.outputSchema !== null) {
|
|
193
|
+
// A truncated structured emission is mid-object and unparseable.
|
|
194
|
+
return fail("content_invalid", "Gemini structured output truncated (finishReason=MAX_TOKENS)", "raise maxOutputTokens for this route or reduce thinking budget", usage);
|
|
195
|
+
}
|
|
196
|
+
return { ok: true, text, usage };
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
200
|
+
// An abort we triggered is our timeout regardless of message text.
|
|
201
|
+
if (abortController.signal.aborted) {
|
|
202
|
+
return fail("timeout", `Gemini call exceeded timeoutMs=${req.timeoutMs}: ${message}`);
|
|
203
|
+
}
|
|
204
|
+
const { cls, fixHint } = classifyGeminiFailure(message);
|
|
205
|
+
return {
|
|
206
|
+
ok: false,
|
|
207
|
+
failure: {
|
|
208
|
+
class: cls,
|
|
209
|
+
message,
|
|
210
|
+
provider: "gemini",
|
|
211
|
+
model,
|
|
212
|
+
cause: error,
|
|
213
|
+
...(fixHint !== undefined ? { fixHint } : {}),
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
clearTimeout(timeoutId);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok (xAI) ProviderAdapter — provider expansion 2026-08-02 (Matt: "add
|
|
3
|
+
* Grok to the testing asap"). OpenAI-compatible API at api.x.ai/v1 with
|
|
4
|
+
* native strict json_schema structured outputs and automatic prompt caching
|
|
5
|
+
* (cached tokens surface in prompt_tokens_details, deepseek-style). No
|
|
6
|
+
* service tiers. Reasoning models keep their trace server-side — only
|
|
7
|
+
* delta.content is accumulated, mirroring the deepseek reasoner rule.
|
|
8
|
+
*
|
|
9
|
+
* Portions may be derived from LiteLLM (https://github.com/BerriAI/litellm),
|
|
10
|
+
* Copyright (c) 2023 Berri AI, MIT License — retain this notice if LiteLLM's
|
|
11
|
+
* xai transformation logic is consulted during the port.
|
|
12
|
+
*/
|
|
13
|
+
import type { CapabilityFlag, ProviderAdapter } from "../types/provider";
|
|
14
|
+
import type { ClassifiedFailure, ShapingInput } from "./gemini";
|
|
15
|
+
export interface GrokAdapterConfig {
|
|
16
|
+
readonly apiKey: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function grokSupports(feature: CapabilityFlag, _model: string): boolean;
|
|
19
|
+
export declare function classifyGrokFailure(message: string): ClassifiedFailure;
|
|
20
|
+
interface ChatMessage {
|
|
21
|
+
readonly role: "system" | "user";
|
|
22
|
+
readonly content: string;
|
|
23
|
+
}
|
|
24
|
+
export interface GrokShapedParams {
|
|
25
|
+
readonly model: string;
|
|
26
|
+
readonly max_tokens: number;
|
|
27
|
+
readonly messages: readonly ChatMessage[];
|
|
28
|
+
readonly stream: true;
|
|
29
|
+
readonly stream_options: {
|
|
30
|
+
readonly include_usage: true;
|
|
31
|
+
};
|
|
32
|
+
readonly response_format?: {
|
|
33
|
+
readonly type: "json_schema";
|
|
34
|
+
readonly json_schema: {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly schema: object;
|
|
37
|
+
readonly strict: true;
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export declare function buildGrokParams(model: string, req: ShapingInput): GrokShapedParams;
|
|
42
|
+
export declare function createGrokAdapter(config: GrokAdapterConfig): ProviderAdapter;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=grok.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grok.d.ts","sourceRoot":"","sources":["../../src/adapters/grok.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAIV,cAAc,EACd,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAKD,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAa7E;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAsBtE;AAED,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IACtB,QAAQ,CAAC,cAAc,EAAE;QAAE,QAAQ,CAAC,aAAa,EAAE,IAAI,CAAA;KAAE,CAAC;IAC1D,QAAQ,CAAC,eAAe,CAAC,EAAE;QACzB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,WAAW,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;SAAE,CAAC;KACjG,CAAC;CACH;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,GAAG,gBAAgB,CAwBlF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,eAAe,CA4G5E"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Grok (xAI) ProviderAdapter — provider expansion 2026-08-02 (Matt: "add
|
|
4
|
+
* Grok to the testing asap"). OpenAI-compatible API at api.x.ai/v1 with
|
|
5
|
+
* native strict json_schema structured outputs and automatic prompt caching
|
|
6
|
+
* (cached tokens surface in prompt_tokens_details, deepseek-style). No
|
|
7
|
+
* service tiers. Reasoning models keep their trace server-side — only
|
|
8
|
+
* delta.content is accumulated, mirroring the deepseek reasoner rule.
|
|
9
|
+
*
|
|
10
|
+
* Portions may be derived from LiteLLM (https://github.com/BerriAI/litellm),
|
|
11
|
+
* Copyright (c) 2023 Berri AI, MIT License — retain this notice if LiteLLM's
|
|
12
|
+
* xai transformation logic is consulted during the port.
|
|
13
|
+
*/
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.grokSupports = grokSupports;
|
|
19
|
+
exports.classifyGrokFailure = classifyGrokFailure;
|
|
20
|
+
exports.buildGrokParams = buildGrokParams;
|
|
21
|
+
exports.createGrokAdapter = createGrokAdapter;
|
|
22
|
+
const openai_1 = __importDefault(require("openai"));
|
|
23
|
+
const GROK_BASE_URL = "https://api.x.ai/v1";
|
|
24
|
+
const DEFAULT_MAX_TOKENS = 32000;
|
|
25
|
+
function grokSupports(feature, _model) {
|
|
26
|
+
switch (feature) {
|
|
27
|
+
case "structured_native":
|
|
28
|
+
case "json_mode":
|
|
29
|
+
case "prefix_cache_implicit":
|
|
30
|
+
return true;
|
|
31
|
+
case "grounded_native": // Live Search exists but is not wired — probes may grant later
|
|
32
|
+
case "context_cache_explicit":
|
|
33
|
+
case "prompt_cache_key":
|
|
34
|
+
case "tier_flex":
|
|
35
|
+
case "trace_visible":
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function classifyGrokFailure(message) {
|
|
40
|
+
const msg = message.toLowerCase();
|
|
41
|
+
if (/\b429\b|rate limit|\brate\b/.test(msg))
|
|
42
|
+
return { cls: "capacity_shed" };
|
|
43
|
+
if (/\b50[023]\b|service unavailable|overloaded/.test(msg))
|
|
44
|
+
return { cls: "capacity_shed" };
|
|
45
|
+
if (/timeout|etimedout|abort/.test(msg))
|
|
46
|
+
return { cls: "timeout" };
|
|
47
|
+
if (/econnreset|econnrefused|enotfound|fetch failed|network/.test(msg))
|
|
48
|
+
return { cls: "network" };
|
|
49
|
+
if (/\b401\b|api key|incorrect api key/.test(msg)) {
|
|
50
|
+
// F2: non-retryable — a dead key never burns backoff retries.
|
|
51
|
+
return { cls: "config_auth", fixHint: "check the xAI API key configured for this rig" };
|
|
52
|
+
}
|
|
53
|
+
if (/credit|spending limit|insufficient_quota|billing/.test(msg)) {
|
|
54
|
+
// xAI 403 "used all available credits / monthly spending limit" —
|
|
55
|
+
// account-level hard stop observed 2026-08-03; retries are pure waste.
|
|
56
|
+
return { cls: "config_auth", fixHint: "the xAI account is out of credits or over its spending limit" };
|
|
57
|
+
}
|
|
58
|
+
if (/does not exist|not found|\bmodel\b.*invalid/.test(msg)) {
|
|
59
|
+
return {
|
|
60
|
+
cls: "network",
|
|
61
|
+
fixHint: "the candidate model was not found — fix the model id in the route's candidate list",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { cls: "network" };
|
|
65
|
+
}
|
|
66
|
+
function buildGrokParams(model, req) {
|
|
67
|
+
const messages = [
|
|
68
|
+
...(req.systemPrompt !== "" ? [{ role: "system", content: req.systemPrompt }] : []),
|
|
69
|
+
{ role: "user", content: req.userPrompt },
|
|
70
|
+
];
|
|
71
|
+
return {
|
|
72
|
+
model,
|
|
73
|
+
max_tokens: req.maxOutputTokens ?? DEFAULT_MAX_TOKENS,
|
|
74
|
+
messages,
|
|
75
|
+
stream: true,
|
|
76
|
+
stream_options: { include_usage: true },
|
|
77
|
+
...(req.outputSchema !== null
|
|
78
|
+
? {
|
|
79
|
+
response_format: {
|
|
80
|
+
type: "json_schema",
|
|
81
|
+
json_schema: {
|
|
82
|
+
name: "modelrig_route_output",
|
|
83
|
+
schema: req.outputSchema,
|
|
84
|
+
strict: true,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createGrokAdapter(config) {
|
|
92
|
+
return {
|
|
93
|
+
id: "grok",
|
|
94
|
+
supports: grokSupports,
|
|
95
|
+
async call(req) {
|
|
96
|
+
const model = req.candidate.model;
|
|
97
|
+
// F1: post-stream failures still billed tokens — attach usage.
|
|
98
|
+
const fail = (cls, message, fixHint, usage) => ({
|
|
99
|
+
ok: false,
|
|
100
|
+
failure: {
|
|
101
|
+
class: cls,
|
|
102
|
+
message,
|
|
103
|
+
provider: "grok",
|
|
104
|
+
model,
|
|
105
|
+
...(fixHint !== undefined ? { fixHint } : {}),
|
|
106
|
+
},
|
|
107
|
+
...(usage !== undefined ? { usage } : {}),
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const client = new openai_1.default({
|
|
111
|
+
apiKey: config.apiKey,
|
|
112
|
+
baseURL: GROK_BASE_URL,
|
|
113
|
+
timeout: req.timeoutMs,
|
|
114
|
+
});
|
|
115
|
+
const params = buildGrokParams(model, req);
|
|
116
|
+
const stream = await client.chat.completions.create(params);
|
|
117
|
+
let text = "";
|
|
118
|
+
let promptTokens = 0;
|
|
119
|
+
let completionTokens = 0;
|
|
120
|
+
let cachedTokens = 0;
|
|
121
|
+
let finishReason = null;
|
|
122
|
+
// Only delta.content is accumulated — a reasoning model's trace must
|
|
123
|
+
// never leak into the JSON buffer (deepseek reasoner rule applied).
|
|
124
|
+
for await (const chunk of stream) {
|
|
125
|
+
const choice = chunk.choices[0];
|
|
126
|
+
if (choice?.delta?.content)
|
|
127
|
+
text += choice.delta.content;
|
|
128
|
+
if (choice?.finish_reason)
|
|
129
|
+
finishReason = choice.finish_reason;
|
|
130
|
+
if (chunk.usage) {
|
|
131
|
+
promptTokens = chunk.usage.prompt_tokens ?? 0;
|
|
132
|
+
completionTokens = chunk.usage.completion_tokens ?? 0;
|
|
133
|
+
cachedTokens = chunk.usage.prompt_tokens_details?.cached_tokens ?? 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// xAI has no service tiers — served is always standard.
|
|
137
|
+
const usage = {
|
|
138
|
+
tokensIn: promptTokens,
|
|
139
|
+
tokensOut: completionTokens,
|
|
140
|
+
tokensCached: cachedTokens,
|
|
141
|
+
servedTier: "standard",
|
|
142
|
+
};
|
|
143
|
+
if (text.trim().length === 0) {
|
|
144
|
+
return fail("content_invalid", `empty response from Grok (finish_reason=${finishReason ?? "none"})`, undefined, usage);
|
|
145
|
+
}
|
|
146
|
+
if (finishReason === "length" && req.outputSchema !== null) {
|
|
147
|
+
return fail("content_invalid", "Grok structured output truncated (finish_reason=length)", "raise maxOutputTokens for this route", usage);
|
|
148
|
+
}
|
|
149
|
+
return { ok: true, text, usage };
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
153
|
+
const { cls, fixHint } = classifyGrokFailure(message);
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
failure: {
|
|
157
|
+
class: cls,
|
|
158
|
+
message,
|
|
159
|
+
provider: "grok",
|
|
160
|
+
model,
|
|
161
|
+
cause: error,
|
|
162
|
+
...(fixHint !== undefined ? { fixHint } : {}),
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|