@aexol/spectral 0.9.230 → 0.9.234
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/CHANGELOG.md +2 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +89 -14
- package/dist/backend/generated-images.d.ts +1 -1
- package/dist/backend/generated-images.d.ts.map +1 -1
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +12 -1
- package/dist/designer/data/skills/blog-post.d.ts +3 -0
- package/dist/designer/data/skills/blog-post.d.ts.map +1 -0
- package/dist/designer/data/skills/blog-post.js +84 -0
- package/dist/designer/data/skills/blog-post.ts +87 -0
- package/dist/designer/data/skills/critique.d.ts +3 -0
- package/dist/designer/data/skills/critique.d.ts.map +1 -0
- package/dist/designer/data/skills/critique.js +109 -0
- package/dist/designer/data/skills/critique.ts +112 -0
- package/dist/designer/data/skills/dashboard.d.ts +3 -0
- package/dist/designer/data/skills/dashboard.d.ts.map +1 -0
- package/dist/designer/data/skills/dashboard.js +165 -0
- package/dist/designer/data/skills/dashboard.ts +168 -0
- package/dist/designer/data/skills/design-brief.d.ts +3 -0
- package/dist/designer/data/skills/design-brief.d.ts.map +1 -0
- package/dist/designer/data/skills/design-brief.js +141 -0
- package/dist/designer/data/skills/design-brief.ts +144 -0
- package/dist/designer/data/skills/design-skill.d.ts +10 -0
- package/dist/designer/data/skills/design-skill.d.ts.map +1 -0
- package/dist/designer/data/skills/design-skill.js +1 -0
- package/dist/designer/data/skills/design-skill.ts +9 -0
- package/dist/designer/data/skills/docs-page.d.ts +3 -0
- package/dist/designer/data/skills/docs-page.d.ts.map +1 -0
- package/dist/designer/data/skills/docs-page.js +68 -0
- package/dist/designer/data/skills/docs-page.ts +71 -0
- package/dist/designer/data/skills/index.d.ts +3 -0
- package/dist/designer/data/skills/index.d.ts.map +1 -0
- package/dist/designer/data/skills/index.js +28 -0
- package/dist/designer/data/skills/index.ts +30 -0
- package/dist/designer/data/skills/magazine-poster.d.ts +3 -0
- package/dist/designer/data/skills/magazine-poster.d.ts.map +1 -0
- package/dist/designer/data/skills/magazine-poster.js +70 -0
- package/dist/designer/data/skills/magazine-poster.ts +73 -0
- package/dist/designer/data/skills/mobile-app.d.ts +3 -0
- package/dist/designer/data/skills/mobile-app.d.ts.map +1 -0
- package/dist/designer/data/skills/mobile-app.js +229 -0
- package/dist/designer/data/skills/mobile-app.ts +232 -0
- package/dist/designer/data/skills/motion-frames.d.ts +3 -0
- package/dist/designer/data/skills/motion-frames.d.ts.map +1 -0
- package/dist/designer/data/skills/motion-frames.js +121 -0
- package/dist/designer/data/skills/motion-frames.ts +124 -0
- package/dist/designer/data/skills/pricing-page.d.ts +3 -0
- package/dist/designer/data/skills/pricing-page.d.ts.map +1 -0
- package/dist/designer/data/skills/pricing-page.js +76 -0
- package/dist/designer/data/skills/pricing-page.ts +79 -0
- package/dist/designer/data/skills/saas-landing.d.ts +3 -0
- package/dist/designer/data/skills/saas-landing.d.ts.map +1 -0
- package/dist/designer/data/skills/saas-landing.js +71 -0
- package/dist/designer/data/skills/saas-landing.ts +74 -0
- package/dist/designer/data/skills/slide-deck.d.ts +3 -0
- package/dist/designer/data/skills/slide-deck.d.ts.map +1 -0
- package/dist/designer/data/skills/slide-deck.js +213 -0
- package/dist/designer/data/skills/slide-deck.ts +216 -0
- package/dist/designer/data/skills/social-carousel.d.ts +3 -0
- package/dist/designer/data/skills/social-carousel.d.ts.map +1 -0
- package/dist/designer/data/skills/social-carousel.js +135 -0
- package/dist/designer/data/skills/social-carousel.ts +138 -0
- package/dist/designer/data/skills/web-prototype.d.ts +3 -0
- package/dist/designer/data/skills/web-prototype.d.ts.map +1 -0
- package/dist/designer/data/skills/web-prototype.js +159 -0
- package/dist/designer/data/skills/web-prototype.ts +162 -0
- package/dist/designer/skills.d.ts +4 -10
- package/dist/designer/skills.d.ts.map +1 -1
- package/dist/designer/skills.js +2 -1643
- package/dist/extensions/logic-trace.d.ts +1 -1
- package/dist/extensions/logic-trace.d.ts.map +1 -1
- package/dist/extensions/logic-trace.js +199 -70
- package/dist/extensions/video-generation/index.d.ts +3 -0
- package/dist/extensions/video-generation/index.d.ts.map +1 -0
- package/dist/extensions/video-generation/index.js +299 -0
- package/dist/extensions/wizard/index.js +9 -1
- package/dist/generated/parser/ast/nodes.d.ts +115 -0
- package/dist/generated/parser/ast/nodes.d.ts.map +1 -0
- package/dist/generated/parser/ast/nodes.js +8 -0
- package/dist/generated/parser/lexer/lexer.d.ts +28 -0
- package/dist/generated/parser/lexer/lexer.d.ts.map +1 -0
- package/dist/generated/parser/{lexer.js → lexer/lexer.js} +1 -180
- package/dist/generated/parser/{lexer.d.ts → lexer/tokens.d.ts} +5 -24
- package/dist/generated/parser/lexer/tokens.d.ts.map +1 -0
- package/dist/generated/parser/lexer/tokens.js +187 -0
- package/dist/generated/parser/mod.d.ts +5 -7
- package/dist/generated/parser/mod.d.ts.map +1 -1
- package/dist/generated/parser/mod.js +2 -1
- package/dist/generated/parser/parser/cursor.d.ts +47 -0
- package/dist/generated/parser/parser/cursor.d.ts.map +1 -0
- package/dist/generated/parser/parser/cursor.js +12 -0
- package/dist/generated/parser/parser/definitions.d.ts +16 -0
- package/dist/generated/parser/parser/definitions.d.ts.map +1 -0
- package/dist/generated/parser/parser/definitions.js +353 -0
- package/dist/generated/parser/parser/expressions.d.ts +17 -0
- package/dist/generated/parser/parser/expressions.d.ts.map +1 -0
- package/dist/generated/parser/parser/expressions.js +165 -0
- package/dist/generated/parser/parser.d.ts +18 -133
- package/dist/generated/parser/parser.d.ts.map +1 -1
- package/dist/generated/parser/parser.js +9 -505
- package/dist/generated/parser/tokenizer.d.ts +1 -2
- package/dist/generated/parser/tokenizer.d.ts.map +1 -1
- package/dist/generated/parser/tokenizer.js +2 -1
- package/dist/generated/parser/validator.d.ts +1 -1
- package/dist/generated/parser/validator.d.ts.map +1 -1
- package/dist/generated/zeus/const.d.ts.map +1 -1
- package/dist/generated/zeus/const.js +30 -0
- package/dist/generated/zeus/index.d.ts +140 -0
- package/dist/generated/zeus/index.d.ts.map +1 -1
- package/dist/generated/zeus/index.js +5 -0
- package/dist/mcp/glimpse-ui.js +9 -1
- package/dist/relay/dispatcher.d.ts +23 -104
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +21 -1130
- package/dist/relay/rest.d.ts +31 -0
- package/dist/relay/rest.d.ts.map +1 -0
- package/dist/relay/rest.js +85 -0
- package/dist/relay/routing.d.ts +109 -0
- package/dist/relay/routing.d.ts.map +1 -0
- package/dist/relay/routing.js +669 -0
- package/dist/relay/wizard-login.d.ts +17 -0
- package/dist/relay/wizard-login.d.ts.map +1 -0
- package/dist/relay/wizard-login.js +419 -0
- package/dist/sdk/agent-core/agent-loop.d.ts +2 -9
- package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent-loop.js +17 -291
- package/dist/sdk/agent-core/agent.d.ts +11 -4
- package/dist/sdk/agent-core/agent.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent.js +16 -4
- package/dist/sdk/agent-core/tool-call-registry.d.ts +75 -0
- package/dist/sdk/agent-core/tool-call-registry.d.ts.map +1 -0
- package/dist/sdk/agent-core/tool-call-registry.js +131 -0
- package/dist/sdk/agent-core/tool-execution.d.ts +33 -0
- package/dist/sdk/agent-core/tool-execution.d.ts.map +1 -0
- package/dist/sdk/agent-core/tool-execution.js +399 -0
- package/dist/sdk/agent-core/types.d.ts +15 -4
- package/dist/sdk/agent-core/types.d.ts.map +1 -1
- package/dist/sdk/ai/env-api-keys.js +9 -1
- package/dist/sdk/ai/index.d.ts +3 -0
- package/dist/sdk/ai/index.d.ts.map +1 -1
- package/dist/sdk/ai/index.js +3 -0
- package/dist/sdk/ai/providers/faux.d.ts.map +1 -1
- package/dist/sdk/ai/providers/faux.js +3 -0
- package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts +39 -6
- package/dist/sdk/ai/providers/leaked-tool-call-parser.d.ts.map +1 -1
- package/dist/sdk/ai/providers/leaked-tool-call-parser.js +159 -18
- package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts +48 -0
- package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts.map +1 -0
- package/dist/sdk/ai/providers/openai/reasoning-replay.js +209 -0
- package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.js +23 -211
- package/dist/sdk/ai/providers/openrouter-videos.d.ts +3 -0
- package/dist/sdk/ai/providers/openrouter-videos.d.ts.map +1 -0
- package/dist/sdk/ai/providers/openrouter-videos.js +412 -0
- package/dist/sdk/ai/providers/register-builtins.d.ts.map +1 -1
- package/dist/sdk/ai/providers/register-builtins.js +6 -0
- package/dist/sdk/ai/providers/simple-options.d.ts +1 -6
- package/dist/sdk/ai/providers/simple-options.d.ts.map +1 -1
- package/dist/sdk/ai/providers/simple-options.js +0 -22
- package/dist/sdk/ai/providers/transform-messages.d.ts.map +1 -1
- package/dist/sdk/ai/providers/transform-messages.js +52 -1
- package/dist/sdk/ai/types.d.ts +91 -10
- package/dist/sdk/ai/types.d.ts.map +1 -1
- package/dist/sdk/ai/video-models.d.ts +10 -0
- package/dist/sdk/ai/video-models.d.ts.map +1 -0
- package/dist/sdk/ai/video-models.generated.d.ts +440 -0
- package/dist/sdk/ai/video-models.generated.d.ts.map +1 -0
- package/dist/sdk/ai/video-models.generated.js +441 -0
- package/dist/sdk/ai/video-models.js +22 -0
- package/dist/sdk/ai/videos-api-registry.d.ts +14 -0
- package/dist/sdk/ai/videos-api-registry.d.ts.map +1 -0
- package/dist/sdk/ai/videos-api-registry.js +21 -0
- package/dist/sdk/ai/videos.d.ts +4 -0
- package/dist/sdk/ai/videos.d.ts.map +1 -0
- package/dist/sdk/ai/videos.js +13 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts +8 -0
- package/dist/sdk/coding-agent/core/agent-session.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/agent-session.js +11 -0
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +6 -118
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/compaction.js +11 -733
- package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts +58 -0
- package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/cut-points.js +191 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts +28 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.js +156 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.d.ts +21 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.js +181 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.d.ts +47 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.js +224 -0
- package/dist/sdk/coding-agent/core/extensions/loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/loader.js +14 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts +26 -0
- package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/native-extensions.js +23 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts +157 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.js +13 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +10 -367
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/types.js +2 -10
- package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts +219 -0
- package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/extensions/ui-types.js +4 -0
- package/dist/sdk/coding-agent/core/messages.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/messages.js +42 -2
- package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts +115 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.js +1184 -0
- package/dist/sdk/coding-agent/core/package/discovery.d.ts +20 -0
- package/dist/sdk/coding-agent/core/package/discovery.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/discovery.js +283 -0
- package/dist/sdk/coding-agent/core/package/ignore.d.ts +29 -0
- package/dist/sdk/coding-agent/core/package/ignore.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/ignore.js +235 -0
- package/dist/sdk/coding-agent/core/package/types.d.ts +128 -0
- package/dist/sdk/coding-agent/core/package/types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/types.js +18 -0
- package/dist/sdk/coding-agent/core/package/update-check.d.ts +43 -0
- package/dist/sdk/coding-agent/core/package/update-check.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/update-check.js +253 -0
- package/dist/sdk/coding-agent/core/package-manager.d.ts +2 -188
- package/dist/sdk/coding-agent/core/package-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/package-manager.js +1 -1940
- package/dist/sdk/coding-agent/core/resource-loader.d.ts +11 -0
- package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/resource-loader.js +23 -6
- package/dist/sdk/coding-agent/core/sdk.d.ts +24 -2
- package/dist/sdk/coding-agent/core/sdk.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/sdk.js +15 -3
- package/dist/sdk/coding-agent/core/session/migrate.d.ts +18 -0
- package/dist/sdk/coding-agent/core/session/migrate.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/session/migrate.js +78 -0
- package/dist/sdk/coding-agent/core/session/types.d.ts +165 -0
- package/dist/sdk/coding-agent/core/session/types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/session/types.js +4 -0
- package/dist/sdk/coding-agent/core/session-manager.d.ts +3 -161
- package/dist/sdk/coding-agent/core/session-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/session-manager.js +4 -72
- package/dist/sdk/coding-agent/core/settings-manager.d.ts +0 -8
- package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +3 -3
- package/dist/sdk/coding-agent/index.d.ts +2 -2
- package/dist/sdk/coding-agent/index.d.ts.map +1 -1
- package/dist/sdk/coding-agent/index.js +1 -1
- package/dist/sdk-public.d.ts +96 -0
- package/dist/sdk-public.d.ts.map +1 -0
- package/dist/sdk-public.js +109 -0
- package/dist/server/agent-bridge/rehydrate.d.ts +2 -2
- package/dist/server/agent-bridge/rehydrate.d.ts.map +1 -1
- package/dist/server/agent-bridge/rehydrate.js +14 -0
- package/dist/server/agent-bridge.d.ts +8 -2
- package/dist/server/agent-bridge.d.ts.map +1 -1
- package/dist/server/agent-bridge.js +25 -2
- package/dist/server/session-stream.d.ts +19 -0
- package/dist/server/session-stream.d.ts.map +1 -1
- package/dist/server/session-stream.js +35 -0
- package/dist/server/wire.d.ts +13 -0
- package/dist/server/wire.d.ts.map +1 -1
- package/package.json +8 -2
- package/dist/generated/parser/lexer.d.ts.map +0 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { getEnvApiKey } from "../env-api-keys.js";
|
|
2
|
+
import { imageSourceUrlForProvider } from "../utils/image-source.js";
|
|
3
|
+
/**
|
|
4
|
+
* Video generation routing (mirrors `openrouter-images.ts`).
|
|
5
|
+
*
|
|
6
|
+
* PRIMARY PATH: when `options.backendUrl` and `options.apiKey` (the machine
|
|
7
|
+
* JWT) are provided, generation is submitted to the backend proxy at
|
|
8
|
+
* `POST ${backendUrl}/video/generation` and polled at
|
|
9
|
+
* `GET ${backendUrl}/video/generation/{videoId}`. The backend runs the
|
|
10
|
+
* upstream OpenRouter job with ITS OWN `OPENROUTER_API_KEY`, stores the
|
|
11
|
+
* finished video in its object storage (`/generated-videos/<id>`) and reports
|
|
12
|
+
* cost — the CLI never needs a local key and never receives video bytes.
|
|
13
|
+
*
|
|
14
|
+
* FALLBACK PATH: when the proxy is unavailable (404/501/5xx, connection
|
|
15
|
+
* errors — the endpoint may not exist yet) or no backend routing is
|
|
16
|
+
* configured (standalone SDK use), we submit + poll + download directly
|
|
17
|
+
* against OpenRouter with a local `OPENROUTER_API_KEY`:
|
|
18
|
+
*
|
|
19
|
+
* POST {baseUrl}/videos -> 202 { id, polling_url, status }
|
|
20
|
+
* GET polling_url (every ~30s, <= 15 min) -> { status: completed|failed, ... }
|
|
21
|
+
* GET {baseUrl}/videos/{id}/content?index=0 (Authorization header; bytes)
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
24
|
+
const DEFAULT_MAX_POLL_MS = 900_000; // 15 minutes overall budget
|
|
25
|
+
const DIRECT_POLL_INTERVAL_MS = 30_000;
|
|
26
|
+
const PROXY_POLL_INTERVAL_MS = 15_000; // the backend polls upstream itself
|
|
27
|
+
const VIDEO_MIME_TYPE = "video/mp4";
|
|
28
|
+
const PASSTHROUGH_KEYS = [
|
|
29
|
+
"duration",
|
|
30
|
+
"resolution",
|
|
31
|
+
"aspect_ratio",
|
|
32
|
+
"size",
|
|
33
|
+
"generate_audio",
|
|
34
|
+
"seed",
|
|
35
|
+
];
|
|
36
|
+
function textFromContext(context) {
|
|
37
|
+
return context.input
|
|
38
|
+
.filter((item) => item.type === "text")
|
|
39
|
+
.map((item) => item.text.trim())
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join("\n\n");
|
|
42
|
+
}
|
|
43
|
+
function framesFromContext(context) {
|
|
44
|
+
return context.input.filter((item) => item.type === "image");
|
|
45
|
+
}
|
|
46
|
+
function usageFromCost(cost) {
|
|
47
|
+
const total = Number(cost ?? 0) || 0;
|
|
48
|
+
return {
|
|
49
|
+
input: 0,
|
|
50
|
+
output: 0,
|
|
51
|
+
cacheRead: 0,
|
|
52
|
+
cacheWrite: 0,
|
|
53
|
+
totalTokens: 0,
|
|
54
|
+
cost: { input: 0, output: total, cacheRead: 0, cacheWrite: 0, total },
|
|
55
|
+
credits: total || undefined,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function errorResult(model, message, aborted) {
|
|
59
|
+
return {
|
|
60
|
+
api: model.api,
|
|
61
|
+
provider: model.provider,
|
|
62
|
+
model: model.id,
|
|
63
|
+
output: [],
|
|
64
|
+
stopReason: aborted ? "aborted" : "error",
|
|
65
|
+
errorMessage: message,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** True for statuses that mean "the proxy endpoint does not exist / is down". */
|
|
70
|
+
function isProxyUnavailableStatus(status) {
|
|
71
|
+
return status === 404 || status === 501 || status === 502 || status === 503 || status === 504;
|
|
72
|
+
}
|
|
73
|
+
function errorMessageFromJson(json, fallback) {
|
|
74
|
+
return json?.error?.message ?? json?.message ?? fallback;
|
|
75
|
+
}
|
|
76
|
+
function fetchJson(url, init) {
|
|
77
|
+
return (async () => {
|
|
78
|
+
const response = await fetch(url, init);
|
|
79
|
+
const rawText = await response.text();
|
|
80
|
+
let json;
|
|
81
|
+
try {
|
|
82
|
+
json = rawText ? JSON.parse(rawText) : {};
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new Error(`Response was not JSON: ${rawText.slice(0, 500)}`);
|
|
86
|
+
}
|
|
87
|
+
return { response, json };
|
|
88
|
+
})();
|
|
89
|
+
}
|
|
90
|
+
function makeSignal(options, deadline) {
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1, Math.min(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, deadline.remaining())));
|
|
93
|
+
if (options.signal) {
|
|
94
|
+
if (options.signal.aborted)
|
|
95
|
+
controller.abort();
|
|
96
|
+
else
|
|
97
|
+
options.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
98
|
+
}
|
|
99
|
+
return { signal: controller.signal, done: () => clearTimeout(timeout) };
|
|
100
|
+
}
|
|
101
|
+
async function sleep(ms, signal) {
|
|
102
|
+
await new Promise((resolvePromise, reject) => {
|
|
103
|
+
const timer = setTimeout(resolvePromise, ms);
|
|
104
|
+
if (signal) {
|
|
105
|
+
if (signal.aborted) {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
signal.addEventListener("abort", () => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
113
|
+
}, { once: true });
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Backend proxy path. Returns `undefined` when the proxy is unavailable so
|
|
119
|
+
* the caller can fall back to direct OpenRouter — never for genuine
|
|
120
|
+
* generation failures (those return an error result).
|
|
121
|
+
*/
|
|
122
|
+
async function generateViaBackendProxy(model, options, backendUrl, machineJwt, prompt, frames) {
|
|
123
|
+
const base = backendUrl.replace(/\/$/, "");
|
|
124
|
+
const body = { model: model.id, prompt };
|
|
125
|
+
for (const key of PASSTHROUGH_KEYS) {
|
|
126
|
+
if (options[key] !== undefined)
|
|
127
|
+
body[key] = options[key];
|
|
128
|
+
}
|
|
129
|
+
// Backend contract: frameImages is [{ data?, mimeType?, path? }] with raw
|
|
130
|
+
// (non data-URI) base64 in `data`.
|
|
131
|
+
if (frames.length > 0) {
|
|
132
|
+
body.frameImages = frames.map((frame) => ({
|
|
133
|
+
data: frame.data,
|
|
134
|
+
mimeType: frame.mimeType,
|
|
135
|
+
}));
|
|
136
|
+
}
|
|
137
|
+
const start = Date.now();
|
|
138
|
+
const maxPollMs = options.maxPollMs ?? DEFAULT_MAX_POLL_MS;
|
|
139
|
+
const deadline = { remaining: () => maxPollMs - (Date.now() - start) };
|
|
140
|
+
const submit = makeSignal(options, deadline);
|
|
141
|
+
let videoId;
|
|
142
|
+
try {
|
|
143
|
+
const { response, json } = await fetchJson(`${base}/video/generation`, {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
"Content-Type": "application/json",
|
|
147
|
+
Authorization: `Bearer ${machineJwt}`,
|
|
148
|
+
...(options.headers ?? {}),
|
|
149
|
+
},
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
signal: submit.signal,
|
|
152
|
+
});
|
|
153
|
+
if (!response.ok) {
|
|
154
|
+
const message = errorMessageFromJson(json, `Backend video generation failed: HTTP ${response.status}`);
|
|
155
|
+
// Fall back to direct OpenRouter only when the proxy itself is
|
|
156
|
+
// missing (bare 404/501/5xx without an upstream error body — e.g.
|
|
157
|
+
// an older backend without the video route). A structured error
|
|
158
|
+
// body means the proxy exists and the upstream rejected the
|
|
159
|
+
// request (e.g. OpenRouter workspace guardrails); surfacing that
|
|
160
|
+
// beats a fallback that would fail identically.
|
|
161
|
+
const hasUpstreamError = Boolean(json?.error?.message ?? json?.message);
|
|
162
|
+
if (isProxyUnavailableStatus(response.status) && !hasUpstreamError)
|
|
163
|
+
return undefined;
|
|
164
|
+
return errorResult(model, message, false);
|
|
165
|
+
}
|
|
166
|
+
videoId = typeof json?.videoId === "string" ? json.videoId : undefined;
|
|
167
|
+
if (!videoId)
|
|
168
|
+
return errorResult(model, "Backend video generation response had no videoId", false);
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
// Connection errors (endpoint may not exist yet) → fall back.
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
submit.done();
|
|
176
|
+
}
|
|
177
|
+
// Poll until COMPLETED/FAILED.
|
|
178
|
+
for (;;) {
|
|
179
|
+
const remaining = deadline.remaining();
|
|
180
|
+
if (remaining <= 0) {
|
|
181
|
+
return errorResult(model, `Video generation timed out after ${maxPollMs / 1000}s (proxy)`, false);
|
|
182
|
+
}
|
|
183
|
+
const pollInterval = options.pollIntervalMs ?? PROXY_POLL_INTERVAL_MS;
|
|
184
|
+
await sleep(Math.min(pollInterval, remaining), options.signal).catch(() => null);
|
|
185
|
+
if (options.signal?.aborted)
|
|
186
|
+
return errorResult(model, "Video generation aborted", true);
|
|
187
|
+
// The sleep may have consumed the rest of the budget — fail with a
|
|
188
|
+
// clean timeout instead of issuing a request that aborts immediately.
|
|
189
|
+
if (deadline.remaining() <= 0) {
|
|
190
|
+
return errorResult(model, `Video generation timed out after ${maxPollMs / 1000}s (proxy)`, false);
|
|
191
|
+
}
|
|
192
|
+
const poll = makeSignal(options, deadline);
|
|
193
|
+
try {
|
|
194
|
+
const { response, json } = await fetchJson(`${base}/video/generation/${videoId}`, {
|
|
195
|
+
method: "GET",
|
|
196
|
+
headers: {
|
|
197
|
+
Authorization: `Bearer ${machineJwt}`,
|
|
198
|
+
...(options.headers ?? {}),
|
|
199
|
+
},
|
|
200
|
+
signal: poll.signal,
|
|
201
|
+
});
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
if (isProxyUnavailableStatus(response.status))
|
|
204
|
+
return undefined;
|
|
205
|
+
return errorResult(model, errorMessageFromJson(json, `Video polling failed: HTTP ${response.status}`), false);
|
|
206
|
+
}
|
|
207
|
+
const status = typeof json?.status === "string" ? json.status.toUpperCase() : "";
|
|
208
|
+
if (status === "COMPLETED") {
|
|
209
|
+
const url = typeof json?.url === "string" && json.url ? json.url : `/generated-videos/${videoId}`;
|
|
210
|
+
const output = [
|
|
211
|
+
{ type: "video", url, videoId, mimeType: VIDEO_MIME_TYPE },
|
|
212
|
+
];
|
|
213
|
+
return {
|
|
214
|
+
api: model.api,
|
|
215
|
+
provider: model.provider,
|
|
216
|
+
model: model.id,
|
|
217
|
+
output,
|
|
218
|
+
usage: usageFromCost(Number(json?.cost)),
|
|
219
|
+
stopReason: "stop",
|
|
220
|
+
responseId: videoId,
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (status === "FAILED") {
|
|
225
|
+
return errorResult(model, json?.error ?? "Video generation failed (proxy)", false);
|
|
226
|
+
}
|
|
227
|
+
options.onProgress?.(`video ${videoId}: ${status || "PENDING"} (${Math.round((Date.now() - start) / 1000)}s)`);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
// Poll request failure (network) → fall back to direct.
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
poll.done();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Direct OpenRouter path: submit -> poll -> download. The download returns
|
|
240
|
+
* raw bytes (not base64), which are carried as base64 in the video block so
|
|
241
|
+
* the extension can save a local copy.
|
|
242
|
+
*/
|
|
243
|
+
async function generateDirect(model, options, apiKey, prompt, frames) {
|
|
244
|
+
const base = model.baseUrl.replace(/\/$/, "");
|
|
245
|
+
const body = { model: model.id, prompt };
|
|
246
|
+
for (const key of PASSTHROUGH_KEYS) {
|
|
247
|
+
if (options[key] !== undefined)
|
|
248
|
+
body[key] = options[key];
|
|
249
|
+
}
|
|
250
|
+
if (frames.length > 0) {
|
|
251
|
+
const frameImages = frames
|
|
252
|
+
.map((frame) => {
|
|
253
|
+
const url = imageSourceUrlForProvider(frame);
|
|
254
|
+
if (!url) {
|
|
255
|
+
options.onProgress?.("Skipping frame image without a resolvable url");
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
return { type: "image_url", image_url: { url } };
|
|
259
|
+
})
|
|
260
|
+
.filter((reference) => reference !== null);
|
|
261
|
+
if (frameImages.length > 0)
|
|
262
|
+
body.frame_images = frameImages;
|
|
263
|
+
}
|
|
264
|
+
const start = Date.now();
|
|
265
|
+
const maxPollMs = options.maxPollMs ?? DEFAULT_MAX_POLL_MS;
|
|
266
|
+
const deadline = { remaining: () => maxPollMs - (Date.now() - start) };
|
|
267
|
+
let videoId;
|
|
268
|
+
let pollingUrl;
|
|
269
|
+
try {
|
|
270
|
+
const submit = makeSignal(options, deadline);
|
|
271
|
+
try {
|
|
272
|
+
const { response, json } = await fetchJson(`${base}/videos`, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: {
|
|
275
|
+
"Content-Type": "application/json",
|
|
276
|
+
Authorization: `Bearer ${apiKey}`,
|
|
277
|
+
"HTTP-Referer": "https://aexol.ai",
|
|
278
|
+
"X-OpenRouter-Title": "Aexol.ai",
|
|
279
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
280
|
+
...(options.headers ?? {}),
|
|
281
|
+
},
|
|
282
|
+
body: JSON.stringify(body),
|
|
283
|
+
signal: submit.signal,
|
|
284
|
+
});
|
|
285
|
+
options.onResponse?.({ status: response.status, headers: Object.fromEntries(response.headers.entries()) }, model);
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
return errorResult(model, errorMessageFromJson(json, `OpenRouter video generation failed: HTTP ${response.status}`), false);
|
|
288
|
+
}
|
|
289
|
+
videoId = typeof json?.id === "string" ? json.id : undefined;
|
|
290
|
+
pollingUrl = typeof json?.polling_url === "string" && json.polling_url ? json.polling_url : undefined;
|
|
291
|
+
if (!videoId && pollingUrl) {
|
|
292
|
+
// Best effort: recover the id from the polling url's last path segment.
|
|
293
|
+
const last = pollingUrl.replace(/\/$/, "").split("/").pop();
|
|
294
|
+
if (last)
|
|
295
|
+
videoId = last;
|
|
296
|
+
}
|
|
297
|
+
if (!videoId) {
|
|
298
|
+
return errorResult(model, "OpenRouter video response had no id or polling_url", false);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
finally {
|
|
302
|
+
submit.done();
|
|
303
|
+
}
|
|
304
|
+
// Poll every ~30s (OpenRouter guidance) until completed/failed.
|
|
305
|
+
for (;;) {
|
|
306
|
+
const remaining = deadline.remaining();
|
|
307
|
+
if (remaining <= 0) {
|
|
308
|
+
return errorResult(model, `Video generation timed out after ${maxPollMs / 1000}s`, false);
|
|
309
|
+
}
|
|
310
|
+
const pollInterval = options.pollIntervalMs ?? DIRECT_POLL_INTERVAL_MS;
|
|
311
|
+
await sleep(Math.min(pollInterval, remaining), options.signal).catch(() => null);
|
|
312
|
+
if (options.signal?.aborted)
|
|
313
|
+
return errorResult(model, "Video generation aborted", true);
|
|
314
|
+
// The sleep may have consumed the rest of the budget — fail with a
|
|
315
|
+
// clean timeout instead of issuing a request that aborts immediately.
|
|
316
|
+
if (deadline.remaining() <= 0) {
|
|
317
|
+
return errorResult(model, `Video generation timed out after ${maxPollMs / 1000}s`, false);
|
|
318
|
+
}
|
|
319
|
+
const poll = makeSignal(options, deadline);
|
|
320
|
+
try {
|
|
321
|
+
const { response, json } = await fetchJson(pollingUrl ?? `${base}/videos/${videoId}`, {
|
|
322
|
+
method: "GET",
|
|
323
|
+
headers: {
|
|
324
|
+
Authorization: `Bearer ${apiKey}`,
|
|
325
|
+
...(options.headers ?? {}),
|
|
326
|
+
},
|
|
327
|
+
signal: poll.signal,
|
|
328
|
+
});
|
|
329
|
+
if (!response.ok) {
|
|
330
|
+
return errorResult(model, errorMessageFromJson(json, `OpenRouter video polling failed: HTTP ${response.status}`), false);
|
|
331
|
+
}
|
|
332
|
+
const status = typeof json?.status === "string" ? json.status.toLowerCase() : "";
|
|
333
|
+
if (status === "completed" || status === "succeeded") {
|
|
334
|
+
const cost = Number(json?.usage?.cost ?? json?.cost ?? 0) || 0;
|
|
335
|
+
// Download the finished video: raw bytes WITH Authorization.
|
|
336
|
+
const download = makeSignal(options, deadline);
|
|
337
|
+
try {
|
|
338
|
+
const downloadResponse = await fetch(`${base}/videos/${videoId}/content?index=0`, {
|
|
339
|
+
method: "GET",
|
|
340
|
+
headers: {
|
|
341
|
+
Authorization: `Bearer ${apiKey}`,
|
|
342
|
+
...(options.headers ?? {}),
|
|
343
|
+
},
|
|
344
|
+
signal: download.signal,
|
|
345
|
+
});
|
|
346
|
+
if (!downloadResponse.ok) {
|
|
347
|
+
return errorResult(model, `OpenRouter video download failed: HTTP ${downloadResponse.status}`, false);
|
|
348
|
+
}
|
|
349
|
+
const buffer = await downloadResponse.arrayBuffer();
|
|
350
|
+
const mimeType = downloadResponse.headers.get("content-type")?.split(";")[0]?.trim() || VIDEO_MIME_TYPE;
|
|
351
|
+
const output = [
|
|
352
|
+
{
|
|
353
|
+
type: "video",
|
|
354
|
+
data: Buffer.from(buffer).toString("base64"),
|
|
355
|
+
mimeType: mimeType.startsWith("video/") ? mimeType : VIDEO_MIME_TYPE,
|
|
356
|
+
},
|
|
357
|
+
];
|
|
358
|
+
return {
|
|
359
|
+
api: model.api,
|
|
360
|
+
provider: model.provider,
|
|
361
|
+
model: model.id,
|
|
362
|
+
output,
|
|
363
|
+
usage: usageFromCost(cost),
|
|
364
|
+
stopReason: "stop",
|
|
365
|
+
responseId: videoId,
|
|
366
|
+
timestamp: Date.now(),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
finally {
|
|
370
|
+
download.done();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (status === "failed") {
|
|
374
|
+
return errorResult(model, json?.error?.message ?? "OpenRouter video generation failed", false);
|
|
375
|
+
}
|
|
376
|
+
options.onProgress?.(`video ${videoId}: ${status || "pending"} (${Math.round((Date.now() - start) / 1000)}s)`);
|
|
377
|
+
}
|
|
378
|
+
finally {
|
|
379
|
+
poll.done();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
return errorResult(model, error instanceof Error ? error.message : String(error), Boolean(options.signal?.aborted));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
export async function generateOpenRouterVideos(model, context, options = {}) {
|
|
388
|
+
const backendUrl = typeof options.backendUrl === "string" ? options.backendUrl : undefined;
|
|
389
|
+
const machineJwt = options.apiKey;
|
|
390
|
+
const routeThroughBackend = Boolean(backendUrl && machineJwt);
|
|
391
|
+
const localApiKey = !routeThroughBackend ? getEnvApiKey("openrouter") : undefined;
|
|
392
|
+
if (!routeThroughBackend && !localApiKey) {
|
|
393
|
+
throw new Error("Missing OPENROUTER_API_KEY for OpenRouter video generation (no backend routing configured)");
|
|
394
|
+
}
|
|
395
|
+
const prompt = textFromContext(context);
|
|
396
|
+
if (!prompt)
|
|
397
|
+
throw new Error("Video generation prompt is required");
|
|
398
|
+
const frames = framesFromContext(context);
|
|
399
|
+
if (routeThroughBackend) {
|
|
400
|
+
const viaProxy = await generateViaBackendProxy(model, options, backendUrl, machineJwt, prompt, frames);
|
|
401
|
+
if (viaProxy)
|
|
402
|
+
return viaProxy;
|
|
403
|
+
}
|
|
404
|
+
// Fallback (not routed, or the proxy was unavailable): direct OpenRouter
|
|
405
|
+
// requires a LOCAL OPENROUTER_API_KEY — the machine JWT must never be
|
|
406
|
+
// sent upstream.
|
|
407
|
+
const fallbackKey = localApiKey ?? getEnvApiKey("openrouter");
|
|
408
|
+
if (!fallbackKey) {
|
|
409
|
+
return errorResult(model, "Backend video proxy unavailable and no OPENROUTER_API_KEY configured for direct fallback", false);
|
|
410
|
+
}
|
|
411
|
+
return generateDirect(model, options, fallbackKey, prompt, frames);
|
|
412
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register-builtins.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/register-builtins.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"register-builtins.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/register-builtins.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAMX,mBAAmB,EACnB,cAAc,EAEd,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAyFxE,eAAO,MAAM,uBAAuB,gEAA0C,CAAC;AAC/E,eAAO,MAAM,6BAA6B,2DAAgD,CAAC;AAE3F;;;GAGG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAclD;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { clearApiProviders, registerApiProvider } from "../api-registry.js";
|
|
2
2
|
import { registerImagesApiProvider } from "../images-api-registry.js";
|
|
3
|
+
import { registerVideosApiProvider } from "../videos-api-registry.js";
|
|
3
4
|
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
4
5
|
import { generateOpenRouterImages } from "./openrouter-images.js";
|
|
6
|
+
import { generateOpenRouterVideos } from "./openrouter-videos.js";
|
|
5
7
|
function createLazyErrorMessage(model, error) {
|
|
6
8
|
return {
|
|
7
9
|
role: "assistant",
|
|
@@ -79,6 +81,10 @@ export function registerBuiltInApiProviders() {
|
|
|
79
81
|
api: "openrouter-images",
|
|
80
82
|
generateImages: generateOpenRouterImages,
|
|
81
83
|
});
|
|
84
|
+
registerVideosApiProvider({
|
|
85
|
+
api: "openrouter-videos",
|
|
86
|
+
generateVideos: generateOpenRouterVideos,
|
|
87
|
+
});
|
|
82
88
|
}
|
|
83
89
|
export function resetApiProviders() {
|
|
84
90
|
clearApiProviders();
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type { Api, Model, SimpleStreamOptions, StreamOptions
|
|
1
|
+
import type { Api, Model, SimpleStreamOptions, StreamOptions } from "../types.js";
|
|
2
2
|
export declare function buildBaseOptions(_model: Model<Api>, options?: SimpleStreamOptions, apiKey?: string): StreamOptions;
|
|
3
|
-
export declare function clampReasoning(effort: ThinkingLevel | undefined): Exclude<ThinkingLevel, "xhigh"> | undefined;
|
|
4
|
-
export declare function adjustMaxTokensForThinking(baseMaxTokens: number | undefined, modelMaxTokens: number, reasoningLevel: ThinkingLevel, customBudgets?: ThinkingBudgets): {
|
|
5
|
-
maxTokens: number;
|
|
6
|
-
thinkingBudget: number;
|
|
7
|
-
};
|
|
8
3
|
//# sourceMappingURL=simple-options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-options.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/simple-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"simple-options.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/simple-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAElF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,CAmBlH"}
|
|
@@ -18,25 +18,3 @@ export function buildBaseOptions(_model, options, apiKey) {
|
|
|
18
18
|
metadata: options?.metadata,
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
export function clampReasoning(effort) {
|
|
22
|
-
return effort === "xhigh" ? "high" : effort;
|
|
23
|
-
}
|
|
24
|
-
export function adjustMaxTokensForThinking(
|
|
25
|
-
// Undefined means no explicit caller cap. Use the model cap and fit thinking inside it.
|
|
26
|
-
baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
|
|
27
|
-
const defaultBudgets = {
|
|
28
|
-
minimal: 1024,
|
|
29
|
-
low: 2048,
|
|
30
|
-
medium: 8192,
|
|
31
|
-
high: 16384,
|
|
32
|
-
};
|
|
33
|
-
const budgets = { ...defaultBudgets, ...customBudgets };
|
|
34
|
-
const minOutputTokens = 1024;
|
|
35
|
-
const level = clampReasoning(reasoningLevel);
|
|
36
|
-
let thinkingBudget = budgets[level];
|
|
37
|
-
const maxTokens = baseMaxTokens === undefined ? modelMaxTokens : Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
|
|
38
|
-
if (maxTokens <= thinkingBudget) {
|
|
39
|
-
thinkingBudget = Math.max(0, maxTokens - minOutputTokens);
|
|
40
|
-
}
|
|
41
|
-
return { maxTokens, thinkingBudget };
|
|
42
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,GAAG,EACH,gBAAgB,EAEhB,OAAO,EACP,KAAK,
|
|
1
|
+
{"version":3,"file":"transform-messages.d.ts","sourceRoot":"","sources":["../../../../src/sdk/ai/providers/transform-messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,GAAG,EACH,gBAAgB,EAEhB,OAAO,EACP,KAAK,EAKL,MAAM,aAAa,CAAC;AAgHrB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAS,GAAG,EACjD,QAAQ,EAAE,OAAO,EAAE,EACnB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAClB,mBAAmB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,KAAK,MAAM,GACxF,OAAO,EAAE,CA8LX"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
2
2
|
const NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
|
|
3
3
|
const IMAGE_GENERATE_SUCCESS_TEXT = "Image generated successfully and shown to the user.";
|
|
4
|
+
const VIDEO_USER_PLACEHOLDER = "(video omitted: models cannot process video)";
|
|
5
|
+
const VIDEO_GENERATE_SUCCESS_TEXT = "Video generated successfully and shown to the user.";
|
|
4
6
|
function replaceImagesWithPlaceholder(content, placeholder) {
|
|
5
7
|
const result = [];
|
|
6
8
|
let previousWasPlaceholder = false;
|
|
@@ -12,6 +14,10 @@ function replaceImagesWithPlaceholder(content, placeholder) {
|
|
|
12
14
|
previousWasPlaceholder = true;
|
|
13
15
|
continue;
|
|
14
16
|
}
|
|
17
|
+
// Video parts are stripped unconditionally earlier (models cannot read
|
|
18
|
+
// video) — defensively skip any that slip through.
|
|
19
|
+
if (block.type === "video")
|
|
20
|
+
continue;
|
|
15
21
|
result.push(block);
|
|
16
22
|
previousWasPlaceholder = block.text === placeholder;
|
|
17
23
|
}
|
|
@@ -40,6 +46,48 @@ function downgradeUnsupportedImages(messages, model) {
|
|
|
40
46
|
return msg;
|
|
41
47
|
});
|
|
42
48
|
}
|
|
49
|
+
function replaceVideosWithPlaceholder(content, placeholder) {
|
|
50
|
+
const result = [];
|
|
51
|
+
let previousWasPlaceholder = false;
|
|
52
|
+
for (const block of content) {
|
|
53
|
+
if (block.type === "video") {
|
|
54
|
+
if (!previousWasPlaceholder) {
|
|
55
|
+
result.push({ type: "text", text: placeholder });
|
|
56
|
+
}
|
|
57
|
+
previousWasPlaceholder = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
result.push(block);
|
|
61
|
+
previousWasPlaceholder = block.type === "text" && block.text === placeholder;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* UNCONDITIONAL video strip: models cannot read video, so video parts are
|
|
67
|
+
* NEVER sent to any model — for every model, not just non-vision ones.
|
|
68
|
+
* The raw block (hosted url or inline bytes) stays on the AgentToolResult for
|
|
69
|
+
* the UI; here it is replaced with a short text note.
|
|
70
|
+
*/
|
|
71
|
+
function stripVideoParts(messages) {
|
|
72
|
+
return messages.map((msg) => {
|
|
73
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
74
|
+
return {
|
|
75
|
+
...msg,
|
|
76
|
+
content: replaceVideosWithPlaceholder(msg.content, VIDEO_USER_PLACEHOLDER),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (msg.role === "toolResult") {
|
|
80
|
+
const replacementText = msg.toolName === "video_generate"
|
|
81
|
+
? VIDEO_GENERATE_SUCCESS_TEXT
|
|
82
|
+
: VIDEO_USER_PLACEHOLDER;
|
|
83
|
+
return {
|
|
84
|
+
...msg,
|
|
85
|
+
content: replaceVideosWithPlaceholder(msg.content, replacementText),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return msg;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
43
91
|
/**
|
|
44
92
|
* Normalize tool call ID for cross-provider compatibility.
|
|
45
93
|
* OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
|
|
@@ -48,7 +96,10 @@ function downgradeUnsupportedImages(messages, model) {
|
|
|
48
96
|
export function transformMessages(messages, model, normalizeToolCallId) {
|
|
49
97
|
// Build a map of original tool call IDs to normalized IDs
|
|
50
98
|
const toolCallIdMap = new Map();
|
|
51
|
-
|
|
99
|
+
// Video blocks are stripped for EVERY model (models cannot read video);
|
|
100
|
+
// images are downgraded only for models without vision support.
|
|
101
|
+
const videoAwareMessages = stripVideoParts(messages);
|
|
102
|
+
const imageAwareMessages = downgradeUnsupportedImages(videoAwareMessages, model);
|
|
52
103
|
// First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)
|
|
53
104
|
const transformed = imageAwareMessages.map((msg) => {
|
|
54
105
|
// User messages pass through unchanged
|