@springbrand/agent-runtime 0.3.0-alpha.2 → 0.3.0-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -9
- package/src/adapter/cloudflare/agent.ts +233 -179
- package/src/adapter/cloudflare/define-agent.ts +252 -104
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/adapter/cloudflare/schema.ts +21 -2
- package/src/adapter/cloudflare/subagent/definition.ts +25 -13
- package/src/adapter/cloudflare/subagent/runner.ts +43 -50
- package/src/adapter/cloudflare/subagent/tools.ts +4 -4
- package/src/adapter/cloudflare/universal-agent/preparation.ts +3 -3
- package/src/adapter/cloudflare/universal-agent/tools.ts +4 -6
- package/src/adapter/cloudflare/workspace/version-control.ts +1 -1
- package/src/config/activation.ts +13 -15
- package/src/config/agent-context.ts +1 -1
- package/src/config/assembler.ts +48 -31
- package/src/config/assembly-snapshot.ts +1 -1
- package/src/config/definition.ts +1 -1
- package/src/config/harness-runtime.ts +77 -49
- package/src/config/index.ts +1 -1
- package/src/config/model-guidance.ts +20 -0
- package/src/config/ports.ts +57 -7
- package/src/config/{pinning.ts → prepared-runtime.ts} +59 -123
- package/src/config/public-contracts.ts +2 -2
- package/src/config/runtime-config.ts +2 -1
- package/src/context/assembly.ts +1 -1
- package/src/context/extension/assembly.ts +2 -2
- package/src/context/memory/budget/gate.ts +19 -3
- package/src/conversation/harness-resume.ts +89 -238
- package/src/conversation/harness-stream.ts +87 -71
- package/src/conversation/history.ts +154 -152
- package/src/conversation/message/contract.ts +28 -4
- package/src/conversation/message/conversion.ts +46 -5
- package/src/conversation/message/projection.ts +61 -21
- package/src/conversation/session-entry.ts +29 -0
- package/src/conversation/state.ts +23 -2
- package/src/harness/governance.ts +206 -0
- package/src/harness/index.ts +3 -0
- package/src/harness/session-harness.ts +269 -445
- package/src/harness/submission-operation.ts +15 -40
- package/src/index.ts +18 -8
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/prompt.ts +9 -10
- package/src/lib/token-estimate.ts +8 -0
- package/src/model-protocol/openrouter.ts +41 -0
- package/src/models/index.ts +200 -35
- package/src/models/openrouter/messages.ts +13 -8
- package/src/models/openrouter/openai.ts +200 -0
- package/src/models/openrouter/protocol-response.ts +85 -0
- package/src/models/provider-streams.ts +45 -0
- package/src/session/cloudflare/migration.ts +382 -864
- package/src/session/cloudflare/storage.ts +13 -536
- package/src/session/index.ts +22 -35
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +20 -30
- package/src/submission/contracts.ts +42 -2
- package/src/submission/delivery.ts +1 -1
- package/src/submission/finalization.ts +36 -15
- package/src/submission/lifecycle.ts +28 -15
- package/src/submission/repository.ts +97 -49
- package/src/submission/steer-delivery.ts +32 -76
- package/src/submission/unattended.ts +40 -0
- package/src/telemetry/contract.ts +0 -1
- package/src/telemetry/coordinator.ts +1 -1
- package/src/telemetry/runtime-telemetry.ts +0 -4
- package/src/telemetry/subagent-lifecycle.ts +1 -1
- package/src/telemetry/turn-events.ts +35 -25
- package/src/tools/README.md +44 -0
- package/src/tools/{ai-adapter.ts → adapters/ai-to-pi.ts} +6 -9
- package/src/tools/{declared.ts → adapters/declared.ts} +6 -2
- package/src/tools/{nested-tools.ts → adapters/pi-to-ai.ts} +4 -4
- package/src/tools/assistant/ask-user.ts +319 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/tools/{time.ts → assistant/time.ts} +1 -1
- package/src/tools/{core-host.ts → codemode/adapter.ts} +50 -53
- package/src/tools/{core.ts → codemode/index.ts} +36 -92
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +29 -26
- package/src/tools/{gateway.ts → mcp/gateway.ts} +19 -19
- package/src/tools/{mcp.ts → mcp/index.ts} +5 -5
- package/src/tools/runtime/activation.ts +40 -0
- package/src/tools/{approval → runtime/approval}/repository.ts +13 -12
- package/src/tools/runtime/compiler.ts +246 -0
- package/src/tools/runtime/contracts.ts +140 -0
- package/src/tools/{decision-gate.ts → runtime/decision-gate.ts} +149 -39
- package/src/tools/{human-decisions.ts → runtime/human-decisions.ts} +4 -4
- package/src/tools/{interaction → runtime/interaction}/repository.ts +1 -7
- package/src/tools/runtime/result.ts +22 -0
- package/src/tools/runtime/unattended-error.ts +4 -0
- package/src/tools/sandbox/index.ts +228 -0
- package/src/tools/schedule/index.ts +301 -0
- package/src/tools/skill/contracts.ts +20 -0
- package/src/tools/skill/index.ts +99 -0
- package/src/tools/skill/resources.ts +322 -0
- package/src/tools/skill/scripts.ts +168 -0
- package/src/tools/{subagent-authority.ts → subagent/authority.ts} +31 -31
- package/src/tools/{subagent.ts → subagent/index.ts} +34 -37
- package/src/tools/{subagent-run-repository.ts → subagent/repository.ts} +1 -1
- package/src/tools/{subagent-runtime.ts → subagent/runtime.ts} +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/approvals.ts +4 -7
- package/src/tools/{temporary-agent → subagent/temporary}/core.ts +2 -3
- package/src/tools/{temporary-agent → subagent/temporary}/runner.ts +1 -1
- package/src/tools/{temporary-agent → subagent/temporary}/workspace.ts +1 -4
- package/src/tools/{web-fetch.ts → web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1322 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/tools/{web-search/web-search.ts → web/search/search.ts} +5 -7
- package/src/tools/{workspace-sandbox.ts → workspace/index.ts} +12 -272
- package/src/tools/{workspace-revision.ts → workspace/revision.ts} +3 -3
- package/src/tools/base.ts +0 -534
- package/src/tools/compiler.ts +0 -327
- package/src/tools/schedule.ts +0 -261
- package/src/tools/skill.ts +0 -565
- package/src/tools/web-search/api.ts +0 -1318
- package/src/tools/web-search/index.ts +0 -2
- package/src/tools/workspace-state-connector.ts +0 -146
- package/src/tools/{approval → runtime/approval}/index.ts +0 -0
- package/src/tools/{interaction → runtime/interaction}/index.ts +0 -0
- package/src/tools/{temporary-agent → subagent/temporary}/coordinator.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/contract.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/extract/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/fanout/index.ts +0 -0
- package/src/tools/{subagent-types → subagent/types}/registry.ts +1 -1
- /package/src/tools/{workspace-versioning.ts → workspace/versioning.ts} +0 -0
|
@@ -1,49 +1,24 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import type { EntryId } from "@earendil-works/pi-durable";
|
|
3
3
|
import type { PiCanonicalUserInput } from "../conversation/message/contract";
|
|
4
4
|
import type { StoredSubmission } from "../submission/repository";
|
|
5
|
-
import type { SessionHarness } from "./session-harness";
|
|
5
|
+
import type { SessionHarness, SessionRunRequest } from "./session-harness";
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/** Reacquire Pi's stable Submission before admitting another model run. */
|
|
8
8
|
export async function prepareSubmissionOperation(
|
|
9
|
-
runtime: SessionHarness
|
|
9
|
+
runtime: SessionHarness,
|
|
10
10
|
submission: Pick<StoredSubmission, "submissionId" | "queuedInputJson" | "regenerateMessageId">,
|
|
11
|
-
regenerationTarget: (messageId: string) =>
|
|
11
|
+
regenerationTarget: (messageId: string) => Promise<EntryId | null>,
|
|
12
12
|
context: Context,
|
|
13
|
-
): Promise<
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
if (
|
|
17
|
-
const navigation = await runtime.run(undefined, context);
|
|
18
|
-
if (navigation.kind !== "settled" || navigation.outcome.status !== "completed") {
|
|
19
|
-
throw new Error("Could not recover the regeneration branch navigation");
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
if (runtime.openOperation) {
|
|
23
|
-
if (runtime.openOperation.operationId !== id) {
|
|
24
|
-
throw new Error(`Submission ${id} does not own the open Harness operation`);
|
|
25
|
-
}
|
|
26
|
-
return { kind: "run" };
|
|
27
|
-
}
|
|
28
|
-
const watch = await runtime.lane.watch(context);
|
|
29
|
-
const previous = watch.snapshot.lastResult;
|
|
30
|
-
watch.unsubscribe();
|
|
31
|
-
if (previous?.operationId === id) return { kind: "settled", outcome: previous };
|
|
32
|
-
if (!submission.queuedInputJson) {
|
|
33
|
-
throw new Error(`Submission ${id} has no open Harness operation or queued input`);
|
|
34
|
-
}
|
|
13
|
+
): Promise<SessionRunRequest> {
|
|
14
|
+
const existing = await runtime.submissionFor(submission.submissionId, context);
|
|
15
|
+
if (existing) return { operationId: submission.submissionId };
|
|
16
|
+
if (!submission.queuedInputJson) throw new Error("The admitted Submission has no canonical input");
|
|
35
17
|
const prompt = JSON.parse(submission.queuedInputJson) as PiCanonicalUserInput;
|
|
36
|
-
if (prompt.role !== "user") throw new Error("
|
|
37
|
-
if (submission.regenerateMessageId
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
operationId: navigationId,
|
|
41
|
-
targetId: regenerationTarget(submission.regenerateMessageId),
|
|
42
|
-
options: { summarize: false },
|
|
43
|
-
}, context);
|
|
44
|
-
if (navigation.kind !== "settled" || navigation.outcome.status !== "completed") {
|
|
45
|
-
throw new Error("Could not navigate to the regeneration branch");
|
|
46
|
-
}
|
|
18
|
+
if (prompt.role !== "user") throw new Error("Invalid canonical user input");
|
|
19
|
+
if (submission.regenerateMessageId) {
|
|
20
|
+
const target = await regenerationTarget(submission.regenerateMessageId);
|
|
21
|
+
await runtime.fork(target, context);
|
|
47
22
|
}
|
|
48
|
-
return {
|
|
23
|
+
return { operationId: submission.submissionId, prompt };
|
|
49
24
|
}
|
package/src/index.ts
CHANGED
|
@@ -51,7 +51,7 @@ export type { RuntimeAssemblyView } from "./config/assembly-view";
|
|
|
51
51
|
export * from "./adapter/cloudflare/define-agent";
|
|
52
52
|
export type { ToolInteractionResponsePreparation } from "./adapter/cloudflare/agent";
|
|
53
53
|
export * from "./conversation/state";
|
|
54
|
-
export * from "./tools/workspace
|
|
54
|
+
export * from "./tools/workspace/versioning";
|
|
55
55
|
export * from "./lib/execution-level";
|
|
56
56
|
export type {
|
|
57
57
|
RuntimeAssemblyInput,
|
|
@@ -84,16 +84,17 @@ export type {
|
|
|
84
84
|
} from "./lib/host-actions";
|
|
85
85
|
export {
|
|
86
86
|
AGENT_TYPES,
|
|
87
|
-
} from "./tools/subagent
|
|
87
|
+
} from "./tools/subagent/types/registry";
|
|
88
88
|
export type {
|
|
89
89
|
AgentType,
|
|
90
|
-
} from "./tools/subagent
|
|
90
|
+
} from "./tools/subagent/types/contract";
|
|
91
91
|
export type {
|
|
92
92
|
PiToolCandidate,
|
|
93
93
|
ToolExposureMode,
|
|
94
94
|
} from "./tools";
|
|
95
95
|
export { basePiToolCandidates } from "./tools";
|
|
96
96
|
export { createPiDeclaredToolCandidate } from "./tools";
|
|
97
|
+
export { harnessToolActivation } from "./tools";
|
|
97
98
|
export type {
|
|
98
99
|
PiDeclaredTool,
|
|
99
100
|
PiDeclaredToolCallContext,
|
|
@@ -126,20 +127,29 @@ export {
|
|
|
126
127
|
export type {
|
|
127
128
|
TemporaryAgentApprovalDecision,
|
|
128
129
|
TemporaryAgentApprovalRequest,
|
|
129
|
-
|
|
130
|
+
TemporaryAgentLaunchSelection,
|
|
130
131
|
TemporaryAgentRequest,
|
|
131
132
|
TemporaryAgentRunContext,
|
|
132
|
-
} from "./tools/temporary
|
|
133
|
-
export { TEMPORARY_AGENT_LAUNCH_KEY } from "./tools/temporary
|
|
133
|
+
} from "./tools/subagent/temporary/core";
|
|
134
|
+
export { TEMPORARY_AGENT_LAUNCH_KEY } from "./tools/subagent/temporary/core";
|
|
134
135
|
export {
|
|
135
136
|
temporaryAgentExtensionIsSafe,
|
|
136
137
|
temporaryAgentToolAllowed,
|
|
137
|
-
} from "./tools/temporary
|
|
138
|
+
} from "./tools/subagent/temporary/runner";
|
|
138
139
|
export {
|
|
139
140
|
createTemporaryAgentWorkspace,
|
|
140
|
-
} from "./tools/temporary
|
|
141
|
+
} from "./tools/subagent/temporary/workspace";
|
|
141
142
|
export {
|
|
142
143
|
createPiModels,
|
|
143
144
|
modelRequestUrl,
|
|
144
145
|
resolvePiModel,
|
|
145
146
|
} from "./models";
|
|
147
|
+
export {
|
|
148
|
+
openRouterRequestUrl,
|
|
149
|
+
requestOpenRouter,
|
|
150
|
+
} from "./model-protocol/openrouter";
|
|
151
|
+
export type { OpenRouterRequestOptions } from "./model-protocol/openrouter";
|
|
152
|
+
export type {
|
|
153
|
+
ModelProtocolResponse,
|
|
154
|
+
OpenRouterProtocol,
|
|
155
|
+
} from "./models/openrouter/protocol-response";
|
package/src/lib/artifacts.ts
CHANGED
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
/** Stable narrow reference shared by messages, RPC responses and the UI. */
|
|
5
5
|
export interface ArtifactRef {
|
|
6
6
|
kind: "artifact_ref";
|
|
7
|
+
artifactId: string;
|
|
7
8
|
path: string;
|
|
8
9
|
bytes: number;
|
|
9
10
|
hash: string;
|
|
11
|
+
workspaceRevision: string;
|
|
12
|
+
availability: "available" | "unavailable";
|
|
10
13
|
preview: string;
|
|
11
14
|
note: string;
|
|
12
15
|
}
|
|
@@ -14,6 +17,15 @@ export interface ArtifactRef {
|
|
|
14
17
|
export interface SpillWorkspace {
|
|
15
18
|
/** 把溢出的 Artifact 内容写入指定 Workspace 路径。 */
|
|
16
19
|
writeFile(path: string, content: string, mimeType?: string): Promise<void>;
|
|
20
|
+
/** 写入后验证物理文件,防止只持久化了一个不存在或被截断的引用。 */
|
|
21
|
+
stat?(path: string): Promise<{
|
|
22
|
+
type: "file" | "directory" | "symlink";
|
|
23
|
+
size: number;
|
|
24
|
+
updatedAt: number;
|
|
25
|
+
contentVersion?: string;
|
|
26
|
+
} | null>;
|
|
27
|
+
/** 写入后验证内容摘要;Artifact 全部是 UTF-8 文本。 */
|
|
28
|
+
readFileBytes?(path: string): Promise<Uint8Array | null>;
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
/**
|
|
@@ -33,6 +45,10 @@ export async function sha256Hex(text: string): Promise<string> {
|
|
|
33
45
|
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
34
46
|
}
|
|
35
47
|
|
|
48
|
+
function artifactId(hash: string): string {
|
|
49
|
+
return `artifact_${hash.slice(0, 24)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
/**
|
|
37
53
|
* 把 Tool 结果转成可存储的文本,并选择 `.txt` 或 `.json` 扩展名。
|
|
38
54
|
*
|
|
@@ -52,3 +68,5 @@ export function serializeOutput(output: unknown): { text: string; ext: "txt" | "
|
|
|
52
68
|
return { text: String(output), ext: "txt" };
|
|
53
69
|
}
|
|
54
70
|
}
|
|
71
|
+
|
|
72
|
+
export { artifactId };
|
|
@@ -25,3 +25,16 @@ export function maxExecutionLevel(
|
|
|
25
25
|
)
|
|
26
26
|
]!;
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/** The most permissive level shared by an Agent and a Scheduled Task ceiling. */
|
|
30
|
+
export function minExecutionLevel(
|
|
31
|
+
left: ExecutionLevel,
|
|
32
|
+
right: ExecutionLevel,
|
|
33
|
+
): ExecutionLevel {
|
|
34
|
+
return EXECUTION_LEVELS[
|
|
35
|
+
Math.min(
|
|
36
|
+
EXECUTION_LEVELS.indexOf(left),
|
|
37
|
+
EXECUTION_LEVELS.indexOf(right),
|
|
38
|
+
)
|
|
39
|
+
]!;
|
|
40
|
+
}
|
package/src/lib/prompt.ts
CHANGED
|
@@ -17,9 +17,8 @@ export const PERSONA =
|
|
|
17
17
|
// Names the stable runtime environment.
|
|
18
18
|
export const RUNTIME =
|
|
19
19
|
"Runtime: this agent runs on Cloudflare Workers. When execute is present, its Code Mode Dynamic Worker is your " +
|
|
20
|
-
"instrument — code there runs with
|
|
21
|
-
"
|
|
22
|
-
"requests, parsing a payload, hitting several known endpoints, or computing over a file. Write plain " +
|
|
20
|
+
"instrument — code there runs with your workspace filesystem (state.*) and the tools.* methods listed in its own description. " +
|
|
21
|
+
"Use it for tool composition, parsing a payload, or computing over a file. Write plain " +
|
|
23
22
|
"JavaScript — the sandbox evaluates " +
|
|
24
23
|
"it directly, so TypeScript type annotations (`: number`, `as Type`) are a syntax error, and there " +
|
|
25
24
|
"is no Python interpreter or package manager to invoke. Use the JS/Workers equivalent of what you " +
|
|
@@ -58,8 +57,7 @@ export const TOOLS =
|
|
|
58
57
|
"use state.* for workspace file operations, tools.* for other host capabilities, and sequential calls for repeated or durable operations. " +
|
|
59
58
|
"Top-level-only Tools remain Direct even when called repeatedly. " +
|
|
60
59
|
"For web tasks, use web_search for web discovery, current facts, cited research, " +
|
|
61
|
-
"and public URL analysis
|
|
62
|
-
"API calls, or when web_search cannot retrieve the required content; do not use execute for ordinary web " +
|
|
60
|
+
"and public URL analysis; do not use execute for ordinary web " +
|
|
63
61
|
"searches. When execute is absent, use the actually exposed Direct Tools. When sandbox_* tools are present, use that isolated Linux environment for Python/Node, " +
|
|
64
62
|
"package managers, system commands, builds, tests and background processes; its filesystem is temporary, " +
|
|
65
63
|
"persistent inputs are copied in automatically on first use, /userspace is read-only, and only explicitly " +
|
|
@@ -70,7 +68,10 @@ export const TOOLS =
|
|
|
70
68
|
"prioritize verification, saving durable results, and the final response. " +
|
|
71
69
|
"When related Tool calls can run independently, all are available inside execute, and execute is present, run them inside that execute rather than as parallel top-level calls; " +
|
|
72
70
|
"only independent top-level-only Direct Tools should be issued together in one turn. When you reference " +
|
|
73
|
-
"code, cite it as file_path:line_number."
|
|
71
|
+
"code, cite it as file_path:line_number. " +
|
|
72
|
+
"When schedule tools are available, prepare a standalone Requirement (at most 1000 Unicode characters), a name (at most 50), a time rule and an explicit timezone. Ask for missing information. " +
|
|
73
|
+
"Use local once/daily/weekly rules for new scheduled tasks. The Host prepares the authorization scope and the user must confirm before saving; never claim a task was created before the Tool succeeds. " +
|
|
74
|
+
"One source Chat may create multiple tasks. Before editing, list tasks and use the exact Task ID; ask when the target is ambiguous. Never attach source history or hidden context to the Requirement.";
|
|
74
75
|
|
|
75
76
|
// A real browser is the only way to observe what a page actually does, and the
|
|
76
77
|
// shared failure mode is a browser tool that returns nothing and looks clean.
|
|
@@ -117,10 +118,8 @@ export const INTERACTION =
|
|
|
117
118
|
"without that answer; required defaults to false. At most one question may request attachments. Do not add an accept field or tell the user to upload through the normal chat composer; " +
|
|
118
119
|
"continue the same turn when its result arrives. " +
|
|
119
120
|
"Don't use it when a sensible default lets you proceed. " +
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
"After it returns, write your complete final answer and make no further Tool calls. Never for small talk or while a " +
|
|
123
|
-
"task is still in progress; it is non-blocking, unlike ask_user.";
|
|
121
|
+
"When a substantive task (report, analysis, or multi-step job) is complete, call suggest_followups once before the final answer. " +
|
|
122
|
+
"Never use it for small talk, quick answers, or while a task is still in progress; it is non-blocking, unlike ask_user.";
|
|
124
123
|
|
|
125
124
|
// Separates short always-on memory from indexed long-tail workspace files.
|
|
126
125
|
export const MEMORY =
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Conservative token estimate for prompt budgeting, not provider billing. */
|
|
2
|
+
export function estimateStringTokens(text: string): number {
|
|
3
|
+
if (!text) return 0;
|
|
4
|
+
return Math.ceil(Math.max(
|
|
5
|
+
text.length / 4,
|
|
6
|
+
text.split(/\s+/u).filter(Boolean).length * 1.3,
|
|
7
|
+
));
|
|
8
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RuntimeModelEndpoint } from "../config/ports";
|
|
2
|
+
|
|
3
|
+
export interface OpenRouterRequestOptions {
|
|
4
|
+
readonly headers?: HeadersInit;
|
|
5
|
+
readonly signal?: AbortSignal;
|
|
6
|
+
readonly fetch?: typeof fetch;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function openRouterRequestUrl(
|
|
10
|
+
endpoint: Pick<RuntimeModelEndpoint, "protocol" | "baseURL">,
|
|
11
|
+
): string {
|
|
12
|
+
const baseURL = endpoint.baseURL.replace(/\/+$/, "");
|
|
13
|
+
switch (endpoint.protocol) {
|
|
14
|
+
case "openrouter-messages":
|
|
15
|
+
return `${baseURL}/v1/messages`;
|
|
16
|
+
case "openrouter-chat":
|
|
17
|
+
return `${baseURL}/chat/completions`;
|
|
18
|
+
case "openrouter-responses":
|
|
19
|
+
return `${baseURL}/responses`;
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(`Not an OpenRouter protocol: ${endpoint.protocol}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function requestOpenRouter(
|
|
26
|
+
endpoint: RuntimeModelEndpoint,
|
|
27
|
+
body: Readonly<Record<string, unknown>>,
|
|
28
|
+
options: OpenRouterRequestOptions = {},
|
|
29
|
+
): Promise<Response> {
|
|
30
|
+
const url = openRouterRequestUrl(endpoint);
|
|
31
|
+
const headers = new Headers(endpoint.headers);
|
|
32
|
+
new Headers(options.headers).forEach((value, name) => headers.set(name, value));
|
|
33
|
+
headers.set("content-type", "application/json");
|
|
34
|
+
headers.set("authorization", `Bearer ${endpoint.apiKey}`);
|
|
35
|
+
return (options.fetch ?? globalThis.fetch)(url, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers,
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
40
|
+
});
|
|
41
|
+
}
|