@mupt-ai/dari-gateway 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -11
- package/dari-gateway.example.yaml +3 -2
- package/dist/context/interaction_fingerprint.js +10 -1
- package/dist/http/auth.js +2 -2
- package/dist/http/http2_server.d.ts +1 -1
- package/dist/http/http2_server.js +3 -3
- package/dist/http/serve.d.ts +1 -0
- package/dist/http/serve.js +5 -4
- package/dist/ingress/chat_completions.js +2 -2
- package/dist/ingress/codex.js +7 -3
- package/dist/ingress/codex_models.d.ts +1 -1
- package/dist/ingress/codex_models.js +22 -13
- package/dist/platform/platform.d.ts +13 -3
- package/dist/protocol/input/openai_responses.js +2 -4
- package/dist/protocol/output/anthropic_messages_output.d.ts +2 -2
- package/dist/protocol/output/anthropic_messages_output.js +1 -0
- package/dist/protocol/output/openai_responses_stream.js +19 -1
- package/dist/providers/execution.d.ts +0 -5
- package/dist/providers/execution.js +5 -8
- package/dist/providers/pi_output.d.ts +4 -0
- package/dist/providers/pi_output.js +1 -1
- package/dist/providers/provider_attempt_runner.d.ts +2 -3
- package/dist/providers/provider_attempt_runner.js +14 -24
- package/dist/providers/provider_call.d.ts +3 -3
- package/dist/providers/provider_call.js +1 -0
- package/dist/providers/stream_finalization.d.ts +0 -1
- package/dist/providers/stream_finalization.js +0 -1
- package/dist/runtime/claude_code/auxiliary_routing.js +1 -9
- package/dist/runtime/explicit_model.js +1 -1
- package/dist/runtime/harness_identity/conversation.d.ts +9 -0
- package/dist/runtime/harness_identity/conversation.js +6 -0
- package/dist/runtime/harness_identity/extract.d.ts +2 -0
- package/dist/runtime/harness_identity/extract.js +20 -5
- package/dist/runtime/harness_identity/index.d.ts +2 -2
- package/dist/runtime/harness_identity/index.js +2 -2
- package/dist/runtime/prepare_routed_completion.d.ts +5 -1
- package/dist/runtime/prepare_routed_completion.js +26 -2
- package/dist/runtime/routed_runtime.js +33 -53
- package/dist/runtime/routing_lease.d.ts +4 -6
- package/dist/runtime/routing_lease.js +2 -47
- package/dist/runtime/runtime_reporter.d.ts +0 -2
- package/dist/runtime/runtime_reporter.js +0 -1
- package/dist/runtime/session_pin.d.ts +7 -0
- package/dist/runtime/session_pin.js +51 -0
- package/dist/self_host/config.d.ts +2 -3
- package/dist/self_host/config.js +3 -3
- package/dist/self_host/main.d.ts +2 -1
- package/dist/self_host/main.js +26 -6
- package/dist/self_host/platform.d.ts +4 -2
- package/dist/self_host/platform.js +6 -2
- package/dist/self_host/resolve.d.ts +8 -1
- package/dist/self_host/resolve.js +31 -8
- package/dist/self_host/session_pin_store.d.ts +14 -0
- package/dist/self_host/session_pin_store.js +44 -0
- package/dist/strategies/priority_selector.d.ts +1 -2
- package/dist/strategies/priority_selector.js +1 -2
- package/dist/strategies/route_decision.d.ts +0 -1
- package/dist/strategies/route_decision.js +0 -3
- package/dist/strategies/routing.d.ts +0 -3
- package/dist/strategies/routing.js +1 -14
- package/dist/telemetry/billing_finalization.d.ts +1 -2
- package/dist/telemetry/billing_finalization.js +1 -1
- package/dist/telemetry/prefix_state.d.ts +0 -1
- package/dist/telemetry/prefix_state.js +1 -3
- package/dist/telemetry/report.d.ts +0 -2
- package/dist/telemetry/report.js +5 -41
- package/dist/types.d.ts +5 -3
- package/package.json +2 -2
- package/src/context/interaction_fingerprint.ts +15 -1
- package/src/http/auth.ts +2 -2
- package/src/http/http2_server.ts +7 -3
- package/src/http/serve.ts +6 -5
- package/src/ingress/chat_completions.ts +2 -1
- package/src/ingress/codex.ts +8 -5
- package/src/ingress/codex_models.ts +18 -7
- package/src/platform/platform.ts +16 -2
- package/src/protocol/input/openai_responses.ts +2 -9
- package/src/protocol/output/anthropic_messages_output.ts +3 -2
- package/src/protocol/output/openai_responses_stream.ts +17 -1
- package/src/providers/execution.ts +5 -15
- package/src/providers/pi_output.ts +1 -1
- package/src/providers/provider_attempt_runner.ts +10 -21
- package/src/providers/provider_call.ts +3 -1
- package/src/providers/stream_finalization.ts +0 -2
- package/src/providers/subscription_limits.ts +1 -1
- package/src/runtime/claude_code/auxiliary_routing.ts +1 -8
- package/src/runtime/explicit_model.ts +1 -1
- package/src/runtime/harness_identity/conversation.ts +16 -0
- package/src/runtime/harness_identity/extract.ts +24 -5
- package/src/runtime/harness_identity/index.ts +3 -0
- package/src/runtime/prepare_routed_completion.ts +32 -2
- package/src/runtime/routed_runtime.ts +39 -59
- package/src/runtime/routing_lease.ts +2 -56
- package/src/runtime/runtime_reporter.ts +0 -3
- package/src/runtime/session_pin.ts +61 -0
- package/src/self_host/config.ts +7 -7
- package/src/self_host/main.ts +29 -6
- package/src/self_host/platform.ts +8 -2
- package/src/self_host/resolve.ts +37 -7
- package/src/self_host/session_pin_store.ts +58 -0
- package/src/strategies/priority_selector.ts +1 -2
- package/src/strategies/route_decision.ts +0 -5
- package/src/strategies/routing.ts +1 -17
- package/src/telemetry/billing_finalization.ts +4 -3
- package/src/telemetry/prefix_state.ts +1 -6
- package/src/telemetry/report.ts +4 -48
- package/src/types.ts +12 -6
- package/dist/strategies/fallback_decision.d.ts +0 -8
- package/dist/strategies/fallback_decision.js +0 -17
- package/src/strategies/fallback_decision.ts +0 -30
package/README.md
CHANGED
|
@@ -36,27 +36,26 @@ The gateway is the same runtime that serves Dari's hosted routing service. Choos
|
|
|
36
36
|
|
|
37
37
|
## Run
|
|
38
38
|
|
|
39
|
-
Requires [Bun](https://bun.sh) 1.3 or later.
|
|
39
|
+
Requires [Bun](https://bun.sh) 1.3 or later. Write a `dari-gateway.yaml` (start from [`dari-gateway.example.yaml`](dari-gateway.example.yaml)), then:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
cp dari-gateway.example.yaml dari-gateway.yaml # edit routers and models
|
|
43
42
|
export DARI_GATEWAY_KEYS="$(openssl rand -hex 24)" # keys your clients send
|
|
44
43
|
export ANTHROPIC_API_KEY=... OPENAI_API_KEY=... # optional with subscriptions
|
|
45
|
-
|
|
46
|
-
bun run start
|
|
44
|
+
bunx @mupt-ai/dari-gateway
|
|
47
45
|
```
|
|
48
46
|
|
|
49
|
-
Or with Docker
|
|
47
|
+
Or with Docker:
|
|
50
48
|
|
|
51
49
|
```bash
|
|
52
|
-
docker build -f dari-gateway/Dockerfile -t dari-gateway .
|
|
53
50
|
docker run -p 8080:8080 \
|
|
54
51
|
-e DARI_GATEWAY_KEYS -e ANTHROPIC_API_KEY \
|
|
55
52
|
-v "$PWD/dari-gateway.yaml:/config/dari-gateway.yaml" \
|
|
56
53
|
-v dari-gateway-data:/data \
|
|
57
|
-
dari-gateway
|
|
54
|
+
ghcr.io/mupt-ai/dari-gateway
|
|
58
55
|
```
|
|
59
56
|
|
|
57
|
+
From a clone, `bun install && bun run start` or `docker build -t dari-gateway .` run the same thing.
|
|
58
|
+
|
|
60
59
|
| Variable | Meaning |
|
|
61
60
|
| --- | --- |
|
|
62
61
|
| `DARI_GATEWAY_KEYS` | Required. Comma-separated bearer keys clients authenticate with. |
|
|
@@ -64,6 +63,7 @@ docker run -p 8080:8080 \
|
|
|
64
63
|
| `DARI_GATEWAY_DB` | SQLite database for subscriptions and conversation state. Default `dari-gateway.db`. It stores subscription tokens in plaintext; protect it like a secret. |
|
|
65
64
|
| `DARI_AUTO_ROUTING_API_KEY` | Optional. Select among a router's models with Dari Auto Routing. |
|
|
66
65
|
| `PORT` | Listen port. Default `8080`. |
|
|
66
|
+
| `DARI_GATEWAY_HOST` | Listen address. Default `127.0.0.1`, so only this machine can connect. The container image sets `0.0.0.0`; set it yourself to expose a process on your network. |
|
|
67
67
|
| Provider keys | Each provider's standard variable, such as `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `META_API_KEY`, or `OPENROUTER_API_KEY`. |
|
|
68
68
|
|
|
69
69
|
## Configure
|
|
@@ -75,8 +75,8 @@ routers:
|
|
|
75
75
|
- anthropic/claude-sonnet-5-5
|
|
76
76
|
- id: openai/gpt-5.5
|
|
77
77
|
thinking: [medium, high]
|
|
78
|
-
fallback: true # retry the next model when a provider fails
|
|
79
78
|
subscriptions: true # prefer connected subscriptions over API keys
|
|
79
|
+
sessionPinning: false # route each session and subagent once, then keep its model
|
|
80
80
|
local: # serves /local/v1/...
|
|
81
81
|
models: [local/qwen3-coder]
|
|
82
82
|
|
|
@@ -106,7 +106,7 @@ In gateway mode the CLI makes no calls to Dari's hosted API. `dari credentials s
|
|
|
106
106
|
|
|
107
107
|
## Embed
|
|
108
108
|
|
|
109
|
-
Deployments with their own storage and accounting compose the runtime with a `GatewayPlatform` (router resolution, conversation state, subscription holds, request records, error reporting) and a selector backend:
|
|
109
|
+
Deployments with their own storage and accounting compose the runtime with a `GatewayPlatform` (router resolution, conversation state, session pins, subscription holds, request records, error reporting) and a selector backend:
|
|
110
110
|
|
|
111
111
|
```ts
|
|
112
112
|
import { createPrioritySelectorBackend, createRequestHandler } from "@mupt-ai/dari-gateway";
|
|
@@ -120,19 +120,26 @@ The root entry needs no Bun APIs, so any runtime with web-standard `fetch`, `Req
|
|
|
120
120
|
Platforms can also:
|
|
121
121
|
|
|
122
122
|
- implement `resolveProviderCredential({ provider, model, routerId, organizationId, apiKey, clientHarness? })` to supply a per-request `{ apiKey, baseUrl?, headers?, subscription? }`, such as a tenant's own key, in place of the router's resolved key. With `subscription` (`"openai_codex"`, `"anthropic_claude_code"`, or `"meta_muse"`), `apiKey` is that plan's access token: Codex's `auth.json` `tokens.access_token`, the token `claude setup-token` prints, or the Model API key a Meta sign-in mints. The platform keeps it fresh. Strict routers serve these supplied plans, but never subscriptions the gateway resolved itself. A plan serves only its own provider's models; a Claude Code plan serves only Claude Code clients, and a Muse Code plan only Muse Code clients (recognized by their `x-client-id`);
|
|
123
|
-
- resolve routers with `strict: true` to serve only the requested `dari/model/<id>` at the requested thinking level, with no selector, lease,
|
|
123
|
+
- resolve routers with `strict: true` to serve only the requested `dari/model/<id>` at the requested thinking level, with no selector, lease, or personal subscription. Set `strict_providers: ["openai"]` on the resolved router to permit any bundled-catalog model from that provider without enumerating `enabled_models`. Use `dari/model/openai/gpt-5.5`; bare IDs and unknown models remain refused. This grant is ignored on non-strict routers and does not discover uncatalogued models;
|
|
124
|
+
- resolve routers with `session_pinning: true` to route each session once and serve that pair for the rest of the session. Each subagent (Claude Code agent id, Codex subagent thread, OpenCode child session) is routed once on its own. Pins are kept through `readSessionPin` and `writeSessionPin`, keyed by router and an opaque pin key; background requests and `dari/model/<id>` requests never use them;
|
|
124
125
|
- read exact usage from `recordRequest`: the summary carries input, output, cache-read, and cache-write tokens and `total_cost_usd`, preferring the provider's billed cost (`cost_source: "provider"`, from OpenRouter and Vercel AI Gateway). Failed and aborted requests are recorded too;
|
|
125
126
|
- attribute cost with `X-Dari-Tags: run=r1,trial=3`, which is parsed into `RequestRecord.tags` and the `recordUsage` payload's `tags`.
|
|
126
127
|
|
|
127
128
|
## Development
|
|
128
129
|
|
|
129
130
|
```bash
|
|
130
|
-
(cd ../dari-router && bun install --frozen-lockfile && bun run build)
|
|
131
131
|
bun install --frozen-lockfile
|
|
132
132
|
bun run typecheck
|
|
133
133
|
bun test
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
This package depends on the published `@mupt-ai/dari-router`. To test against a local router checkout inside the monorepo, build it and link it after installing:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
(cd ../dari-router && bun install --frozen-lockfile && bun run build)
|
|
140
|
+
../scripts/link-gateway-router.sh
|
|
141
|
+
```
|
|
142
|
+
|
|
136
143
|
Tests make no provider or network calls.
|
|
137
144
|
|
|
138
145
|
## License
|
|
@@ -9,11 +9,12 @@ routers:
|
|
|
9
9
|
- anthropic/claude-sonnet-5-5
|
|
10
10
|
- id: openai/gpt-5.5
|
|
11
11
|
thinking: [medium, high]
|
|
12
|
-
# Retry the next model when a provider fails (default true).
|
|
13
|
-
fallback: true
|
|
14
12
|
# Serve with connected Claude Code / ChatGPT subscriptions before API
|
|
15
13
|
# keys (default true).
|
|
16
14
|
subscriptions: true
|
|
15
|
+
# Route each session's first turn, then keep that model for the whole
|
|
16
|
+
# session; each subagent is routed once on its own (default false).
|
|
17
|
+
sessionPinning: false
|
|
17
18
|
|
|
18
19
|
local:
|
|
19
20
|
models:
|
|
@@ -59,7 +59,7 @@ export function interactionLooseChain(routerId, request) {
|
|
|
59
59
|
// transcript step. Growing history and tool changes preserve identity, while
|
|
60
60
|
// unrelated work reusing the same harness session gets a new group.
|
|
61
61
|
export function interactionConversationPrefix(request) {
|
|
62
|
-
const firstStep = conversationFingerprintSteps(looseFingerprintSteps(request.sourceProtocol, request.items)).at(0);
|
|
62
|
+
const firstStep = conversationFingerprintSteps(looseFingerprintSteps(request.sourceProtocol, request.items.filter((item) => !isOpenClawRuntimeContext(item)))).at(0);
|
|
63
63
|
return sha256(stableStringify({
|
|
64
64
|
version: CONVERSATION_PREFIX_VERSION,
|
|
65
65
|
firstStep: firstStep === undefined
|
|
@@ -67,6 +67,15 @@ export function interactionConversationPrefix(request) {
|
|
|
67
67
|
: canonicalInteractionFingerprintStep(firstStep),
|
|
68
68
|
}));
|
|
69
69
|
}
|
|
70
|
+
// OpenClaw appends a user message of runtime context (active exec sessions,
|
|
71
|
+
// subagents) to each request and drops it from later history. On a first
|
|
72
|
+
// turn it merges into the first transcript step, so leaving it in would give
|
|
73
|
+
// the first turn a different conversation than the turns after it.
|
|
74
|
+
const OPENCLAW_RUNTIME_CONTEXT = "<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>";
|
|
75
|
+
function isOpenClawRuntimeContext(item) {
|
|
76
|
+
return item.type === "message" && item.role === "user" && item.content.length > 0 &&
|
|
77
|
+
item.content.every((part) => part.type === "text" && part.text.trimStart().startsWith(OPENCLAW_RUNTIME_CONTEXT));
|
|
78
|
+
}
|
|
70
79
|
export function interactionPrefixContext(routerId, request, targets) {
|
|
71
80
|
const chainByModel = new Map();
|
|
72
81
|
const itemIndexByHashByModel = new Map();
|
package/dist/http/auth.js
CHANGED
|
@@ -4,7 +4,7 @@ export function bearerToken(headers) {
|
|
|
4
4
|
const value = headers.get("authorization") ?? "";
|
|
5
5
|
const [scheme, token] = value.split(/\s+/, 2);
|
|
6
6
|
if (scheme?.toLowerCase() !== "bearer" || !token) {
|
|
7
|
-
throw new HttpError(401, "Missing or invalid
|
|
7
|
+
throw new HttpError(401, "Missing or invalid API key.", "authentication_error");
|
|
8
8
|
}
|
|
9
9
|
return publicRoutingToken(token);
|
|
10
10
|
}
|
|
@@ -14,7 +14,7 @@ export function anthropicToken(headers) {
|
|
|
14
14
|
return bearerToken(headers);
|
|
15
15
|
const apiKey = headers.get("x-api-key");
|
|
16
16
|
if (!apiKey) {
|
|
17
|
-
throw new HttpError(401, "Missing or invalid
|
|
17
|
+
throw new HttpError(401, "Missing or invalid API key.", "authentication_error");
|
|
18
18
|
}
|
|
19
19
|
return publicRoutingToken(apiKey);
|
|
20
20
|
}
|
|
@@ -3,4 +3,4 @@ export type Http2RoutingServer = {
|
|
|
3
3
|
readonly port: number;
|
|
4
4
|
stop(force?: boolean): Promise<void>;
|
|
5
5
|
};
|
|
6
|
-
export declare function startHttp2Server(port: number, handler: Http2RequestHandler): Http2RoutingServer;
|
|
6
|
+
export declare function startHttp2Server(port: number, handler: Http2RequestHandler, hostname?: string): Http2RoutingServer;
|
|
@@ -8,7 +8,7 @@ const HTTP2_FORBIDDEN_RESPONSE_HEADERS = new Set([
|
|
|
8
8
|
"transfer-encoding",
|
|
9
9
|
"upgrade",
|
|
10
10
|
]);
|
|
11
|
-
export function startHttp2Server(port, handler) {
|
|
11
|
+
export function startHttp2Server(port, handler, hostname = "0.0.0.0") {
|
|
12
12
|
const sessions = new Set();
|
|
13
13
|
let closePromise;
|
|
14
14
|
const server = createServer((request, response) => {
|
|
@@ -18,10 +18,10 @@ export function startHttp2Server(port, handler) {
|
|
|
18
18
|
sessions.add(session);
|
|
19
19
|
session.once("close", () => sessions.delete(session));
|
|
20
20
|
});
|
|
21
|
-
server.listen(port,
|
|
21
|
+
server.listen(port, hostname);
|
|
22
22
|
const address = server.address();
|
|
23
23
|
if (address === null || typeof address === "string") {
|
|
24
|
-
throw new Error("HTTP/2
|
|
24
|
+
throw new Error("HTTP/2 gateway server did not bind to a TCP port.");
|
|
25
25
|
}
|
|
26
26
|
return {
|
|
27
27
|
port: address.port,
|
package/dist/http/serve.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ type StoppableServer = {
|
|
|
3
3
|
};
|
|
4
4
|
export declare function startServer(listenPort: number, handler: (request: Request) => Promise<Response>, options?: {
|
|
5
5
|
http2?: boolean;
|
|
6
|
+
hostname?: string;
|
|
6
7
|
}): import("./http2_server.js").Http2RoutingServer | Bun.Server<undefined>;
|
|
7
8
|
/**
|
|
8
9
|
* On SIGTERM/SIGINT: stop accepting work, let active requests finish, drain
|
package/dist/http/serve.js
CHANGED
|
@@ -9,13 +9,14 @@ const FORCE_STOP_SETTLE_TIMEOUT_MS = 250;
|
|
|
9
9
|
const SHUTDOWN_WORK_TIMEOUT_MS = 7_500;
|
|
10
10
|
export function startServer(listenPort, handler, options = {}) {
|
|
11
11
|
if (options.http2) {
|
|
12
|
-
return startHttp2Server(listenPort, handler);
|
|
12
|
+
return startHttp2Server(listenPort, handler, options.hostname);
|
|
13
13
|
}
|
|
14
14
|
// The selector runs before the first response byte, and routed models can
|
|
15
15
|
// remain silent while reasoning. Disable Bun's default 10s idle timeout;
|
|
16
16
|
// clients and infrastructure still bound total request lifetime.
|
|
17
17
|
return Bun.serve({
|
|
18
18
|
port: listenPort,
|
|
19
|
+
hostname: options.hostname,
|
|
19
20
|
fetch: handler,
|
|
20
21
|
idleTimeout: 0,
|
|
21
22
|
});
|
|
@@ -31,14 +32,14 @@ export function installShutdownHandlers(server, closeClients) {
|
|
|
31
32
|
return;
|
|
32
33
|
shuttingDown = true;
|
|
33
34
|
const workDeadlineAtMs = performance.now() + SHUTDOWN_WORK_TIMEOUT_MS;
|
|
34
|
-
console.info("
|
|
35
|
+
console.info("gateway server shutting down", JSON.stringify({ signal }));
|
|
35
36
|
try {
|
|
36
37
|
await stopServerForShutdown(server);
|
|
37
38
|
}
|
|
38
39
|
finally {
|
|
39
40
|
const remaining = await drainBackgroundTelemetryFor(Math.max(0, workDeadlineAtMs - performance.now()));
|
|
40
41
|
if (remaining > 0) {
|
|
41
|
-
console.warn("
|
|
42
|
+
console.warn("gateway server background drain timed out", JSON.stringify({ remaining_task_count: remaining }));
|
|
42
43
|
captureOperationalError(new Error(`Routing shutdown abandoned ${remaining} background task(s).`), { code: "background_drain_timeout" });
|
|
43
44
|
}
|
|
44
45
|
await closeClients();
|
|
@@ -73,6 +74,6 @@ function stopWithin(server, force, timeoutMs) {
|
|
|
73
74
|
]);
|
|
74
75
|
}
|
|
75
76
|
function reportServerStopFailure(error) {
|
|
76
|
-
console.warn("
|
|
77
|
+
console.warn("gateway server shutdown failed", error instanceof Error ? error.name : "UnknownError");
|
|
77
78
|
captureOperationalError(error, { code: "server_force_stop_failed" });
|
|
78
79
|
}
|
|
@@ -4,7 +4,7 @@ import { captureRequestError } from "../observability/errors.js";
|
|
|
4
4
|
import { chatCompletionResponse, chatCompletionsSseStream } from "../protocol/output/chat_completions.js";
|
|
5
5
|
import { chatCompletionsToInteraction } from "../protocol/input/chat_completions.js";
|
|
6
6
|
import { handleRoutedCompletion } from "../runtime/routed_completion.js";
|
|
7
|
-
import { chatCompletionsConversationIdentity, openCodeRequestContext, } from "../runtime/harness_identity/index.js";
|
|
7
|
+
import { chatCompletionsConversationIdentity, openCodeRequestContext, piRequestContext, } from "../runtime/harness_identity/index.js";
|
|
8
8
|
import { parseJsonRequest } from "./json.js";
|
|
9
9
|
import { museCodeClientId } from "./muse_code.js";
|
|
10
10
|
export async function handleChatCompletions(request, routerId, selectorBackend) {
|
|
@@ -12,7 +12,7 @@ export async function handleChatCompletions(request, routerId, selectorBackend)
|
|
|
12
12
|
const apiKey = bearerToken(request.headers);
|
|
13
13
|
const payload = await parseJsonRequest(request);
|
|
14
14
|
const interaction = chatCompletionsToInteraction({ ...payload, source_protocol: "openai_chat" });
|
|
15
|
-
const agentContext = openCodeRequestContext(request.headers);
|
|
15
|
+
const agentContext = piRequestContext(request.headers) ?? openCodeRequestContext(request.headers);
|
|
16
16
|
const museClientId = museCodeClientId(request.headers);
|
|
17
17
|
return await handleRoutedCompletion({
|
|
18
18
|
request,
|
package/dist/ingress/codex.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { explicitModelId } from "../protocol/input/router_model.js";
|
|
1
2
|
import { bearerToken } from "../http/auth.js";
|
|
2
3
|
import { errorResponse } from "../http/errors.js";
|
|
3
4
|
import { captureRequestError } from "../observability/errors.js";
|
|
@@ -7,7 +8,7 @@ import { openAIResponsesToInteraction } from "../protocol/input/openai_responses
|
|
|
7
8
|
import { parseJsonRequest } from "./json.js";
|
|
8
9
|
import { museCodeClientId } from "./muse_code.js";
|
|
9
10
|
import { handleRoutedCompletion } from "../runtime/routed_completion.js";
|
|
10
|
-
import { codexConversationIdentity, codexRequestPurpose, codexRequestContext, } from "../runtime/harness_identity/index.js";
|
|
11
|
+
import { codexConversationIdentity, codexRequestPurpose, codexRequestContext, museCodeConversationIdentity, } from "../runtime/harness_identity/index.js";
|
|
11
12
|
export async function handleResponses(request, routerId, selectorBackend) {
|
|
12
13
|
try {
|
|
13
14
|
const apiKey = bearerToken(request.headers);
|
|
@@ -22,13 +23,16 @@ export async function handleResponses(request, routerId, selectorBackend) {
|
|
|
22
23
|
...(agentContext === undefined ? {} : { agentContext }),
|
|
23
24
|
...(museClientId === undefined ? {} : { museCodeClientId: museClientId }),
|
|
24
25
|
};
|
|
25
|
-
const
|
|
26
|
+
const codexIdentity = codexConversationIdentity(request.headers, rawPayload);
|
|
26
27
|
// Codex applies its global model_reasoning_effort setting to custom model
|
|
27
28
|
// providers. That preference describes a direct model, not a heterogeneous
|
|
28
29
|
// router, so let Dari select the reasoning effort for verified Codex turns.
|
|
29
30
|
// Parsing above still validates malformed Responses reasoning controls.
|
|
30
|
-
if (
|
|
31
|
+
if (codexIdentity !== undefined && explicitModelId(payload.model) === null) {
|
|
31
32
|
payload.reasoning = undefined;
|
|
33
|
+
}
|
|
34
|
+
const harnessConversationIdentity = codexIdentity
|
|
35
|
+
?? (museClientId === undefined ? undefined : museCodeConversationIdentity(request.headers));
|
|
32
36
|
return await handleRoutedCompletion({
|
|
33
37
|
request,
|
|
34
38
|
routerId,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ResolvedRouter } from "../types.js";
|
|
2
|
-
export declare function codexModelsResponse(router: Pick<ResolvedRouter, "enabled_models" | "model_configs" | "allow_long_context">, imageInputFor: (model: string) => boolean): Record<string, unknown>;
|
|
2
|
+
export declare function codexModelsResponse(router: Pick<ResolvedRouter, "enabled_models" | "model_configs" | "allow_long_context" | "autorouting_enabled">, imageInputFor: (model: string) => boolean): Record<string, unknown>;
|
|
@@ -22,8 +22,11 @@ function codexCatalogEntry(args) {
|
|
|
22
22
|
slug: args.slug,
|
|
23
23
|
display_name: args.displayName,
|
|
24
24
|
description: args.description,
|
|
25
|
-
default_reasoning_level: null,
|
|
26
|
-
supported_reasoning_levels: []
|
|
25
|
+
default_reasoning_level: args.defaultReasoningLevel ?? null,
|
|
26
|
+
supported_reasoning_levels: (args.reasoningLevels ?? []).map((effort) => ({
|
|
27
|
+
effort,
|
|
28
|
+
description: `${effort} thinking`,
|
|
29
|
+
})),
|
|
27
30
|
shell_type: "shell_command",
|
|
28
31
|
visibility: "list",
|
|
29
32
|
supported_in_api: true,
|
|
@@ -61,20 +64,22 @@ function codexCatalogEntry(args) {
|
|
|
61
64
|
multi_agent_version: null,
|
|
62
65
|
};
|
|
63
66
|
}
|
|
64
|
-
/* `dari/routing`
|
|
65
|
-
model follows as `dari/model/<id>` so
|
|
67
|
+
/* `dari/routing` comes first while the router may autoroute (the Codex
|
|
68
|
+
launcher patches it); each enabled model follows as `dari/model/<id>` so
|
|
69
|
+
Codex's /model menu can switch to it. The `dari` launchers build every
|
|
70
|
+
agent's model menu from this list. */
|
|
66
71
|
export function codexModelsResponse(router, imageInputFor) {
|
|
67
72
|
return {
|
|
68
73
|
models: [
|
|
69
|
-
codexCatalogEntry({
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
...(router.autorouting_enabled === false ? [] : [codexCatalogEntry({
|
|
75
|
+
slug: CANONICAL_ROUTER_MODEL,
|
|
76
|
+
displayName: "Dari Routing",
|
|
77
|
+
description: "Routes Codex HTTP Responses requests through the configured Dari model router.",
|
|
78
|
+
priority: 0,
|
|
79
|
+
contextWindow: codexRouterContextWindow(router),
|
|
80
|
+
hostedWebSearch: codexRouterSupportsHostedWebSearch(router),
|
|
81
|
+
imageInput: router.enabled_models.some(imageInputFor),
|
|
82
|
+
})]),
|
|
78
83
|
...router.enabled_models.map((model, index) => codexCatalogEntry({
|
|
79
84
|
slug: explicitModelSlug(model),
|
|
80
85
|
displayName: model,
|
|
@@ -83,6 +88,10 @@ export function codexModelsResponse(router, imageInputFor) {
|
|
|
83
88
|
contextWindow: codexRouterContextWindow({ ...router, enabled_models: [model] }),
|
|
84
89
|
hostedWebSearch: codexRouterSupportsHostedWebSearch({ ...router, enabled_models: [model] }),
|
|
85
90
|
imageInput: imageInputFor(model),
|
|
91
|
+
reasoningLevels: router.model_configs?.[model]?.supported_thinking_levels
|
|
92
|
+
?.map((effort) => effort === "off" ? "none" : effort),
|
|
93
|
+
defaultReasoningLevel: router.model_configs?.[model]?.default_thinking_level === "off"
|
|
94
|
+
? "none" : router.model_configs?.[model]?.default_thinking_level,
|
|
86
95
|
})),
|
|
87
96
|
],
|
|
88
97
|
};
|
|
@@ -33,6 +33,10 @@ export type GatewayPlatform = {
|
|
|
33
33
|
writePrefixState(write: PrefixStateWrite): Promise<void>;
|
|
34
34
|
/** Set or clear a conversation's prefetched routing lease. */
|
|
35
35
|
writePendingLease(write: PendingLeaseWrite): Promise<void>;
|
|
36
|
+
/** The pair a session pinned on a session-pinning router, or null. */
|
|
37
|
+
readSessionPin(request: SessionPinKey): Promise<SessionPin | null>;
|
|
38
|
+
/** Pin a session to a pair, replacing any earlier pin. */
|
|
39
|
+
writeSessionPin(write: SessionPinKey & SessionPin): Promise<void>;
|
|
36
40
|
/**
|
|
37
41
|
* Hold an exhausted personal subscription until it resets. Resolves whether
|
|
38
42
|
* another connected subscription for the same provider can serve a retry.
|
|
@@ -84,6 +88,14 @@ export type PendingLeaseWrite = {
|
|
|
84
88
|
output_text: string | null;
|
|
85
89
|
} | null;
|
|
86
90
|
};
|
|
91
|
+
export type SessionPinKey = {
|
|
92
|
+
routerId: string;
|
|
93
|
+
pinKey: string;
|
|
94
|
+
};
|
|
95
|
+
export type SessionPin = {
|
|
96
|
+
model: string;
|
|
97
|
+
reasoningEffort: ReasoningEffort;
|
|
98
|
+
};
|
|
87
99
|
export type SubscriptionHold = {
|
|
88
100
|
userId: string;
|
|
89
101
|
credentialId: string | undefined;
|
|
@@ -117,8 +129,6 @@ export type RequestSummary = {
|
|
|
117
129
|
selected_reasoning_effort: ReasoningEffort | null;
|
|
118
130
|
next_model: string | null;
|
|
119
131
|
next_reasoning_effort: ReasoningEffort | null;
|
|
120
|
-
fallback_model: string | null;
|
|
121
|
-
fallback_reasoning_effort: ReasoningEffort | null;
|
|
122
132
|
provider_key_source: ProviderKeySource | null;
|
|
123
133
|
message_count: number;
|
|
124
134
|
tool_count: number;
|
|
@@ -145,7 +155,7 @@ export type RequestErrorContext = {
|
|
|
145
155
|
sourceProtocol: SourceProtocol | "openai_models" | "anthropic_models";
|
|
146
156
|
status: number;
|
|
147
157
|
};
|
|
148
|
-
export type OperationalErrorCode = "background_drain_timeout" | "billing_usage_report_failed" | "routing_report_assembly_failed" | "routing_report_barrier_failed" | "server_force_stop_failed" | "stream_report_finalization_failed" | "subscription_unavailable_ingest_failed";
|
|
158
|
+
export type OperationalErrorCode = "background_drain_timeout" | "billing_usage_report_failed" | "routing_report_assembly_failed" | "routing_report_barrier_failed" | "server_force_stop_failed" | "session_pin_write_failed" | "stream_report_finalization_failed" | "subscription_unavailable_ingest_failed";
|
|
149
159
|
export type OperationalErrorContext = {
|
|
150
160
|
code: OperationalErrorCode;
|
|
151
161
|
requestId?: string;
|
|
@@ -499,10 +499,8 @@ function reasoningControls(value) {
|
|
|
499
499
|
if (!isRecord(value))
|
|
500
500
|
throw new HttpError(400, "reasoning must be an object.", "invalid_request_error", "reasoning");
|
|
501
501
|
const normalizedEffort = normalizedReasoningEffort(value.effort, "reasoning.effort");
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
throw new HttpError(400, "reasoning.summary is not supported by this router MVP.", "invalid_request_error", "reasoning.summary");
|
|
505
|
-
}
|
|
502
|
+
// Summaries are display-only: clients such as Muse Code ask for them by
|
|
503
|
+
// default, and the router serves the turn without them.
|
|
506
504
|
return normalizedEffort === undefined ? undefined : { effort: normalizedEffort };
|
|
507
505
|
}
|
|
508
506
|
function normalizedReasoningEffort(value, param) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InteractionOutput } from "../../interaction/types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ServedDecision } from "../../types.js";
|
|
3
3
|
export declare function anthropicMessageId(requestId: string): string;
|
|
4
4
|
export declare function anthropicContent(output: InteractionOutput): Array<Record<string, unknown>>;
|
|
5
5
|
export declare function anthropicReasoningBlock(item: Extract<InteractionOutput["items"][number], {
|
|
@@ -7,4 +7,4 @@ export declare function anthropicReasoningBlock(item: Extract<InteractionOutput[
|
|
|
7
7
|
}>): Record<string, unknown>;
|
|
8
8
|
export declare function anthropicStopReason(output: Pick<InteractionOutput, "stopReason">): string;
|
|
9
9
|
export declare function anthropicUsage(output: Pick<InteractionOutput, "usage">): Record<string, unknown>;
|
|
10
|
-
export declare function anthropicRouting(decision:
|
|
10
|
+
export declare function anthropicRouting(decision: ServedDecision, requestId: string, conversationId?: string): Record<string, unknown>;
|
|
@@ -103,6 +103,7 @@ export function anthropicRouting(decision, requestId, conversationId) {
|
|
|
103
103
|
...(decision.leaseTurnsRemaining === undefined
|
|
104
104
|
? {}
|
|
105
105
|
: { lease_turns_remaining: decision.leaseTurnsRemaining }),
|
|
106
|
+
...(decision.sessionPinned === true ? { session_pinned: true } : {}),
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
109
|
function toolInput(kind, value) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { openAIResponsesOutputItem, openAIResponsesOutputItems, openAIResponsesResponseId, openAIResponsesRouting, openAIResponsesTerminal, openAIResponsesUsage, } from "./openai_responses_output.js";
|
|
2
|
-
import { piMessageToInteractionOutput } from "../../providers/pi_output.js";
|
|
2
|
+
import { piMessageToInteractionOutput, textSignature } from "../../providers/pi_output.js";
|
|
3
3
|
import { isProviderFailureMessage, providerFailureFromEvent, providerFailureMessage, } from "../../runtime/provider_stream_outcome.js";
|
|
4
4
|
import { sseStream } from "./sse_stream.js";
|
|
5
5
|
export function openAIResponsesSseStream(args) {
|
|
@@ -78,6 +78,7 @@ class ResponsesAccumulator {
|
|
|
78
78
|
addedIndexes = new Set();
|
|
79
79
|
completedIndexes = new Set();
|
|
80
80
|
messageIds = new Map();
|
|
81
|
+
messagePhases = new Map();
|
|
81
82
|
reasoningIds = new Map();
|
|
82
83
|
messagePartStarted = new Set();
|
|
83
84
|
reasoningPartStarted = new Set();
|
|
@@ -102,6 +103,8 @@ class ResponsesAccumulator {
|
|
|
102
103
|
this.activate(index, send);
|
|
103
104
|
if (type === "thinking_start")
|
|
104
105
|
this.ensureReasoning(index, send);
|
|
106
|
+
if (type === "text_start")
|
|
107
|
+
this.rememberMessageSignature(event, index);
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
107
110
|
if (type === "text_delta" && typeof event.delta === "string" && event.delta) {
|
|
@@ -273,18 +276,31 @@ class ResponsesAccumulator {
|
|
|
273
276
|
currentUsage() {
|
|
274
277
|
return openAIResponsesUsage(this.finalOutput ?? this.baseOutput());
|
|
275
278
|
}
|
|
279
|
+
// Pi signs a Responses text block with its message id and phase at
|
|
280
|
+
// text_start, so the item is announced with the phase it ends with.
|
|
281
|
+
rememberMessageSignature(event, index) {
|
|
282
|
+
const content = isRecord(event.partial) ? event.partial.content : undefined;
|
|
283
|
+
const block = Array.isArray(content) ? content[event.contentIndex] : undefined;
|
|
284
|
+
const signature = isRecord(block) ? textSignature(block.textSignature) : null;
|
|
285
|
+
if (signature?.id && !this.messageIds.has(index))
|
|
286
|
+
this.messageIds.set(index, signature.id);
|
|
287
|
+
if (signature?.phase)
|
|
288
|
+
this.messagePhases.set(index, signature.phase);
|
|
289
|
+
}
|
|
276
290
|
ensureMessage(index, send) {
|
|
277
291
|
const existing = this.items.get(index);
|
|
278
292
|
if (existing?.type === "message")
|
|
279
293
|
return existing;
|
|
280
294
|
const id = this.messageIds.get(index) ?? streamedItemId("msg", this.responseId, index);
|
|
281
295
|
this.messageIds.set(index, id);
|
|
296
|
+
const phase = this.messagePhases.get(index);
|
|
282
297
|
const message = {
|
|
283
298
|
type: "message",
|
|
284
299
|
id,
|
|
285
300
|
role: "assistant",
|
|
286
301
|
content: [{ type: "text", text: "" }],
|
|
287
302
|
contentShape: "parts",
|
|
303
|
+
...(phase ? { phase } : {}),
|
|
288
304
|
source: this.args.source,
|
|
289
305
|
};
|
|
290
306
|
this.items.set(index, message);
|
|
@@ -297,6 +313,7 @@ class ResponsesAccumulator {
|
|
|
297
313
|
id,
|
|
298
314
|
status: "in_progress",
|
|
299
315
|
content: [],
|
|
316
|
+
...(phase ? { phase } : {}),
|
|
300
317
|
},
|
|
301
318
|
});
|
|
302
319
|
this.sendMessagePartAdded(index, id, send);
|
|
@@ -363,6 +380,7 @@ class ResponsesAccumulator {
|
|
|
363
380
|
id: serialized.id,
|
|
364
381
|
status: "in_progress",
|
|
365
382
|
content: [],
|
|
383
|
+
...(item.phase ? { phase: item.phase } : {}),
|
|
366
384
|
},
|
|
367
385
|
});
|
|
368
386
|
}
|
|
@@ -7,9 +7,7 @@ import { type ProviderExecution, type StartedCompletion, type StartedStream } fr
|
|
|
7
7
|
export type ProviderChoice = {
|
|
8
8
|
decision: RoutingDecision;
|
|
9
9
|
execution: ProviderExecution;
|
|
10
|
-
role: "primary" | "fallback";
|
|
11
10
|
};
|
|
12
|
-
export type ResolveFallbackChoice = () => Promise<ProviderChoice | null>;
|
|
13
11
|
export type PreparedStreamExecution = {
|
|
14
12
|
choice: ProviderChoice;
|
|
15
13
|
started: StartedStream;
|
|
@@ -27,7 +25,6 @@ export declare function executeNonStreaming(args: {
|
|
|
27
25
|
interaction: InteractionRequest;
|
|
28
26
|
primary: ProviderChoice;
|
|
29
27
|
primaryRetries: number;
|
|
30
|
-
resolveFallback: ResolveFallbackChoice;
|
|
31
28
|
reporter: RoutedRuntimeReporter;
|
|
32
29
|
protocol: RoutedProtocol;
|
|
33
30
|
startedCompletion?: StartedCompletion;
|
|
@@ -39,7 +36,6 @@ export declare function completeNonStreaming(args: {
|
|
|
39
36
|
interaction: InteractionRequest;
|
|
40
37
|
primary: ProviderChoice;
|
|
41
38
|
primaryRetries: number;
|
|
42
|
-
resolveFallback: ResolveFallbackChoice;
|
|
43
39
|
reporter: RoutedRuntimeReporter;
|
|
44
40
|
protocol: RoutedProtocol;
|
|
45
41
|
startedCompletion?: StartedCompletion;
|
|
@@ -51,7 +47,6 @@ export declare function prepareStreamingExecution(args: {
|
|
|
51
47
|
interaction: InteractionRequest;
|
|
52
48
|
primary: ProviderChoice;
|
|
53
49
|
primaryRetries: number;
|
|
54
|
-
resolveFallback: ResolveFallbackChoice;
|
|
55
50
|
reporter: RoutedRuntimeReporter;
|
|
56
51
|
startedStream?: StartedStream;
|
|
57
52
|
initialError?: unknown;
|
|
@@ -58,7 +58,7 @@ export async function completeNonStreaming(args) {
|
|
|
58
58
|
output,
|
|
59
59
|
modelCost: args.reporter.modelCost({
|
|
60
60
|
model: choice.decision.selectedModel, reasoning_effort: choice.decision.reasoningEffort,
|
|
61
|
-
|
|
61
|
+
attempt: result.attempt, outcome: "completed", billingOutcome: "completed",
|
|
62
62
|
execution: reportedExecution(choice.execution), message: completion.message,
|
|
63
63
|
}),
|
|
64
64
|
request: args.interaction,
|
|
@@ -121,7 +121,6 @@ async function completeWithRetries(args) {
|
|
|
121
121
|
return runProviderAttemptPlan({
|
|
122
122
|
primary: args.primary,
|
|
123
123
|
retries: args.primaryRetries,
|
|
124
|
-
resolveFallback: args.resolveFallback,
|
|
125
124
|
primaryInitial: args.startedCompletion,
|
|
126
125
|
isCustomerCanceled: () => customerAbortRequested(args.clientSignal),
|
|
127
126
|
canceledFailure: customerCanceledProviderMessage,
|
|
@@ -255,7 +254,6 @@ export async function prepareStreamingExecution(args) {
|
|
|
255
254
|
const result = await runProviderAttemptPlan({
|
|
256
255
|
primary: args.primary,
|
|
257
256
|
retries: args.primaryRetries,
|
|
258
|
-
resolveFallback: args.resolveFallback,
|
|
259
257
|
primaryInitial,
|
|
260
258
|
isCustomerCanceled: () => customerAbortRequested(args.clientSignal),
|
|
261
259
|
canceledFailure: customerCanceledProviderMessage,
|
|
@@ -307,7 +305,7 @@ async function runStreamSetupAttempt(args) {
|
|
|
307
305
|
clientSignal: args.clientSignal,
|
|
308
306
|
});
|
|
309
307
|
// Prime before committing so connection failures and prompt rejections
|
|
310
|
-
// stay inside the one shared retry
|
|
308
|
+
// stay inside the one shared retry state machine.
|
|
311
309
|
const primed = await primeProviderStream(started.providerStream.events, started.customerAbort);
|
|
312
310
|
if (primed.outcome === "error") {
|
|
313
311
|
started.customerAbort.dispose();
|
|
@@ -349,7 +347,6 @@ export async function executeStreaming(args) {
|
|
|
349
347
|
reporter: args.reporter,
|
|
350
348
|
protocol: args.protocol,
|
|
351
349
|
providerLatencyMs: () => args.failedSetupLatencyMs + Date.now() - started.providerStartedMs,
|
|
352
|
-
role: args.choice.role,
|
|
353
350
|
attempt: args.attempt,
|
|
354
351
|
reportBarrier: args.reportBarrier,
|
|
355
352
|
delivered: observed.delivered,
|
|
@@ -367,7 +364,7 @@ export async function executeStreaming(args) {
|
|
|
367
364
|
clientSignal: args.clientSignal,
|
|
368
365
|
modelCost: (message, failed) => args.reporter.modelCost({
|
|
369
366
|
model: args.choice.decision.selectedModel, reasoning_effort: args.choice.decision.reasoningEffort,
|
|
370
|
-
|
|
367
|
+
attempt: args.attempt,
|
|
371
368
|
outcome: failed ? "terminal_error" : "completed",
|
|
372
369
|
billingOutcome: failed ? "provider_failed" : "completed",
|
|
373
370
|
execution: reportedExecution(execution), message,
|
|
@@ -396,7 +393,6 @@ function recordProviderAttempt(reporter, choice, attempt, outcome, billingOutcom
|
|
|
396
393
|
reporter.recordProviderAttempt({
|
|
397
394
|
model: choice.decision.selectedModel,
|
|
398
395
|
reasoning_effort: choice.decision.reasoningEffort,
|
|
399
|
-
role: choice.role,
|
|
400
396
|
attempt,
|
|
401
397
|
outcome,
|
|
402
398
|
execution: reportedExecution(choice.execution),
|
|
@@ -424,7 +420,8 @@ async function providerFailedAttempt(message, latencyMs, execution, responseCapt
|
|
|
424
420
|
}
|
|
425
421
|
// Retrying an exhausted plan cannot succeed. The platform learns about it
|
|
426
422
|
// before this request returns so the next resolve already serves the
|
|
427
|
-
// user's next subscription, or the
|
|
423
|
+
// user's next subscription, or the model's API credential when the router
|
|
424
|
+
// allows subscription API fallback for it.
|
|
428
425
|
const switched = execution.subscriptionUserId !== undefined
|
|
429
426
|
&& await holdSubscription({
|
|
430
427
|
userId: execution.subscriptionUserId,
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { InteractionOutput, InteractionRequest, ProviderIdentity } from "../interaction/types.js";
|
|
2
2
|
export declare function piMessageToInteractionOutput(message: Record<string, unknown>, source: ProviderIdentity, request: Pick<InteractionRequest, "tools">): InteractionOutput;
|
|
3
|
+
export declare function textSignature(value: unknown): {
|
|
4
|
+
id?: string;
|
|
5
|
+
phase?: "commentary" | "final_answer";
|
|
6
|
+
} | null;
|
|
@@ -364,7 +364,7 @@ function reasoningText(value, type) {
|
|
|
364
364
|
return [];
|
|
365
365
|
return value.flatMap((entry) => isRecord(entry) && entry.type === type && typeof entry.text === "string" ? [entry.text] : []);
|
|
366
366
|
}
|
|
367
|
-
function textSignature(value) {
|
|
367
|
+
export function textSignature(value) {
|
|
368
368
|
if (typeof value !== "string" || !value)
|
|
369
369
|
return null;
|
|
370
370
|
try {
|
|
@@ -31,7 +31,6 @@ export type ProviderAttemptPlanResult<TChoice, TValue> = {
|
|
|
31
31
|
type RunProviderAttemptPlanArgs<TChoice, TValue, TInitial> = {
|
|
32
32
|
primary: TChoice;
|
|
33
33
|
retries: number;
|
|
34
|
-
resolveFallback: () => Promise<TChoice | null>;
|
|
35
34
|
primaryInitial?: TInitial;
|
|
36
35
|
isCustomerCanceled: (choice: TChoice) => boolean;
|
|
37
36
|
canceledFailure: () => Record<string, unknown>;
|
|
@@ -48,8 +47,8 @@ type RunProviderAttemptPlanArgs<TChoice, TValue, TInitial> = {
|
|
|
48
47
|
};
|
|
49
48
|
/**
|
|
50
49
|
* Owns the order-sensitive provider plan shared by completion and stream setup:
|
|
51
|
-
* retries within
|
|
52
|
-
*
|
|
50
|
+
* retries within the choice, cancellation fences between attempts, and one
|
|
51
|
+
* contiguous attempt record for every failed dispatch.
|
|
53
52
|
*/
|
|
54
53
|
export declare function runProviderAttemptPlan<TChoice, TValue, TInitial>(args: RunProviderAttemptPlanArgs<TChoice, TValue, TInitial>): Promise<ProviderAttemptPlanResult<TChoice, TValue>>;
|
|
55
54
|
export {};
|