@velum-labs/routekit-gateway 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/registry.js +1 -1
- package/dist/adapters/anthropic-codec.d.ts +1 -1
- package/dist/adapters/anthropic-codec.js +15 -4
- package/dist/adapters/anthropic-stream.js +29 -12
- package/dist/adapters/anthropic-wire.d.ts +5 -1
- package/dist/adapters/anthropic.d.ts +1 -1
- package/dist/adapters/anthropic.js +5 -3
- package/dist/adapters/cursor.js +2 -2
- package/dist/adapters/responses-codec.d.ts +12 -1
- package/dist/adapters/responses-codec.js +37 -9
- package/dist/adapters/responses-stream.d.ts +1 -1
- package/dist/adapters/responses-stream.js +4 -35
- package/dist/adapters/responses-wire.d.ts +2 -1
- package/dist/adapters/responses.d.ts +1 -1
- package/dist/adapters/responses.js +9 -8
- package/dist/adapters/server-tool-loop.d.ts +2 -2
- package/dist/adapters/server-tool-loop.js +5 -5
- package/dist/adapters/server-tool-transcript.d.ts +1 -1
- package/dist/adapters/server-tool-transcript.js +1 -1
- package/dist/adapters/standalone-listener.d.ts +6 -0
- package/dist/adapters/standalone-listener.js +26 -0
- package/dist/adapters/web-search.js +1 -1
- package/dist/annotations/anthropic-stream.d.ts +4 -0
- package/dist/annotations/anthropic-stream.js +45 -0
- package/dist/annotations/chat-stream.d.ts +4 -0
- package/dist/annotations/chat-stream.js +53 -0
- package/dist/annotations/codec.d.ts +23 -0
- package/dist/annotations/codec.js +335 -0
- package/dist/annotations/protocol.d.ts +77 -0
- package/dist/annotations/protocol.js +26 -0
- package/dist/annotations/response.d.ts +6 -0
- package/dist/annotations/response.js +76 -0
- package/dist/annotations/responses-stream.d.ts +7 -0
- package/dist/annotations/responses-stream.js +137 -0
- package/dist/annotations/sse-transform.d.ts +7 -0
- package/dist/annotations/sse-transform.js +148 -0
- package/dist/annotations/wire.d.ts +22 -0
- package/dist/annotations/wire.js +124 -0
- package/dist/catalog-service.d.ts +10 -1
- package/dist/catalog-service.js +101 -23
- package/dist/effect-api.d.ts +2 -1
- package/dist/effect-api.js +1 -1
- package/dist/endpoints/anthropic-messages-endpoint.d.ts +2 -1
- package/dist/endpoints/anthropic-messages-endpoint.js +46 -49
- package/dist/endpoints/chat-endpoint.d.ts +1 -1
- package/dist/endpoints/chat-endpoint.js +24 -38
- package/dist/endpoints/endpoint-module.d.ts +1 -1
- package/dist/endpoints/endpoint-module.js +1 -1
- package/dist/endpoints/models-endpoint.d.ts +1 -1
- package/dist/endpoints/models-endpoint.js +2 -2
- package/dist/endpoints/request-preparation.d.ts +15 -0
- package/dist/endpoints/request-preparation.js +40 -0
- package/dist/endpoints/responses-endpoint.d.ts +2 -1
- package/dist/endpoints/responses-endpoint.js +31 -53
- package/dist/fetch-api.d.ts +11 -0
- package/dist/fetch-api.js +7 -0
- package/dist/gateway-service.d.ts +29 -1
- package/dist/gateway-service.js +79 -61
- package/dist/http/app.d.ts +9 -1
- package/dist/http/app.js +84 -21
- package/dist/http/auth.d.ts +6 -0
- package/dist/http/auth.js +53 -1
- package/dist/http/errors.d.ts +0 -11
- package/dist/http/errors.js +10 -30
- package/dist/http/request.d.ts +2 -2
- package/dist/http/request.js +21 -26
- package/dist/index.d.ts +24 -38
- package/dist/index.js +12 -20
- package/dist/model-call-service.d.ts +22 -7
- package/dist/model-call-service.js +327 -79
- package/dist/observability/cost.d.ts +20 -2
- package/dist/observability/cost.js +97 -19
- package/dist/observability/provenance.d.ts +5 -2
- package/dist/observability/provenance.js +376 -21
- package/dist/observability/routing-cost-episode.d.ts +11 -0
- package/dist/observability/routing-cost-episode.js +29 -0
- package/dist/providers/codex-model-selection.d.ts +4 -2
- package/dist/providers/codex-model-selection.js +43 -34
- package/dist/providers/routekit-gateway-source.d.ts +20 -0
- package/dist/providers/routekit-gateway-source.js +85 -0
- package/dist/routing/classifier.d.ts +8 -0
- package/dist/routing/classifier.js +124 -15
- package/dist/routing/compositional.d.ts +1 -1
- package/dist/routing/core.d.ts +8 -5
- package/dist/routing/core.js +21 -4
- package/dist/routing/eval-policy.d.ts +1 -0
- package/dist/routing/eval-policy.js +10 -5
- package/dist/routing/requirements.d.ts +1 -1
- package/dist/routing/router.d.ts +30 -10
- package/dist/routing/router.js +195 -70
- package/dist/routing-api.d.ts +3 -2
- package/dist/routing-api.js +2 -1
- package/dist/server-api.d.ts +2 -2
- package/dist/server-api.js +1 -1
- package/dist/services/edge-annotations/service.d.ts +30 -0
- package/dist/services/edge-annotations/service.js +33 -0
- package/dist/switching-proxy.d.ts +7 -1
- package/dist/switching-proxy.js +52 -27
- package/dist/test/anthropic-stop-reason.test.js +1 -1
- package/dist/test/anthropic.test.js +304 -3
- package/dist/test/chat.test.js +109 -3
- package/dist/test/cluster-gateway-fixture.d.ts +10 -0
- package/dist/test/cluster-gateway-fixture.js +49 -0
- package/dist/test/cluster-gateway.test.js +133 -0
- package/dist/test/codex-content.test.d.ts +1 -0
- package/dist/test/codex-content.test.js +119 -0
- package/dist/test/codex-model-selection.test.js +144 -123
- package/dist/test/compositional-routing.test.js +45 -0
- package/dist/test/cost.test.js +60 -0
- package/dist/test/cursor.test.js +2 -2
- package/dist/test/drain.test.js +1 -1
- package/dist/test/dropped.test.js +1 -1
- package/dist/test/edge-annotation-codec.test.d.ts +1 -0
- package/dist/test/edge-annotation-codec.test.js +220 -0
- package/dist/test/edge-annotation-egress.test.d.ts +1 -0
- package/dist/test/edge-annotation-egress.test.js +300 -0
- package/dist/test/edge-annotation-ingress.test.d.ts +1 -0
- package/dist/test/edge-annotation-ingress.test.js +284 -0
- package/dist/test/edge-annotation-observation.test.d.ts +1 -0
- package/dist/test/edge-annotation-observation.test.js +100 -0
- package/dist/test/edge-annotation-plumbing.test.d.ts +1 -0
- package/dist/test/edge-annotation-plumbing.test.js +64 -0
- package/dist/test/effect-gateway.test.js +1 -1
- package/dist/test/endpoint-modules.test.js +1 -1
- package/dist/test/fetch-host.test.d.ts +1 -0
- package/dist/test/fetch-host.test.js +282 -0
- package/dist/test/foundry-provider-exposure.test.d.ts +1 -0
- package/dist/test/foundry-provider-exposure.test.js +203 -0
- package/dist/test/http-submission-server-tools.test.d.ts +1 -0
- package/dist/test/http-submission-server-tools.test.js +199 -0
- package/dist/test/openai-reasoning.test.js +5 -26
- package/dist/test/openai-responses-wire.test.js +30 -1
- package/dist/test/provenance-stream-errors.test.d.ts +1 -0
- package/dist/test/provenance-stream-errors.test.js +78 -0
- package/dist/test/provenance.test.js +413 -1
- package/dist/test/provider-backends-fixtures.d.ts +1 -1
- package/dist/test/provider-google-codex.test.js +55 -11
- package/dist/test/provider-protocol.test.js +69 -1
- package/dist/test/provider-source-fixture.d.ts +1 -2
- package/dist/test/provider-source-fixture.js +1 -4
- package/dist/test/provider-streaming.test.js +24 -5
- package/dist/test/provider-validation-anthropic.test.js +82 -23
- package/dist/test/published-catalog.test.d.ts +1 -0
- package/dist/test/published-catalog.test.js +196 -0
- package/dist/test/request-classifier.test.js +71 -1
- package/dist/test/responses-conversion.test.js +2 -2
- package/dist/test/responses-fixtures.d.ts +1 -1
- package/dist/test/responses-fixtures.js +2 -2
- package/dist/test/responses-reasoning.test.js +151 -7
- package/dist/test/responses-streaming-catalog.test.js +254 -2
- package/dist/test/responses-tools.test.js +2 -2
- package/dist/test/routed-backend.test.js +28 -3
- package/dist/test/routekit-gateway-source.test.d.ts +1 -0
- package/dist/test/routekit-gateway-source.test.js +74 -0
- package/dist/test/router.test.js +95 -32
- package/dist/test/server-resilience.test.js +2 -2
- package/dist/test/sse-codec.test.js +2 -2
- package/dist/test/standalone-listener.test.d.ts +1 -0
- package/dist/test/standalone-listener.test.js +43 -0
- package/dist/test/switching-proxy-retire.test.js +7 -2
- package/dist/test/upstream-diagnostics.test.d.ts +1 -0
- package/dist/test/upstream-diagnostics.test.js +155 -0
- package/dist/test/web-search-loop.test.js +4 -4
- package/package.json +13 -14
- package/dist/adapters/dropped.d.ts +0 -10
- package/dist/adapters/dropped.js +0 -24
- package/dist/adapters/openai-chat-wire.d.ts +0 -153
- package/dist/adapters/openai-chat-wire.js +0 -916
- package/dist/adapters/openai-responses-wire.d.ts +0 -42
- package/dist/adapters/openai-responses-wire.js +0 -352
- package/dist/capacity-pool.d.ts +0 -2
- package/dist/capacity-pool.js +0 -1
- package/dist/effect/gateway.d.ts +0 -6
- package/dist/effect/gateway.js +0 -16
- package/dist/http/response.d.ts +0 -5
- package/dist/http/response.js +0 -32
- package/dist/protocol-api.d.ts +0 -2
- package/dist/protocol-api.js +0 -1
- package/dist/providers/anthropic-backend.d.ts +0 -11
- package/dist/providers/anthropic-backend.js +0 -52
- package/dist/providers/anthropic-codec.d.ts +0 -15
- package/dist/providers/anthropic-codec.js +0 -678
- package/dist/providers/auth.d.ts +0 -6
- package/dist/providers/auth.js +0 -51
- package/dist/providers/backend-core.d.ts +0 -81
- package/dist/providers/backend-core.js +0 -135
- package/dist/providers/backend-factory.d.ts +0 -8
- package/dist/providers/backend-factory.js +0 -19
- package/dist/providers/backend.d.ts +0 -139
- package/dist/providers/backend.js +0 -129
- package/dist/providers/backends.d.ts +0 -5
- package/dist/providers/backends.js +0 -4
- package/dist/providers/bedrock-codec.d.ts +0 -11
- package/dist/providers/bedrock-codec.js +0 -484
- package/dist/providers/bedrock-source.d.ts +0 -37
- package/dist/providers/bedrock-source.js +0 -426
- package/dist/providers/codex-responses-backend.d.ts +0 -15
- package/dist/providers/codex-responses-backend.js +0 -120
- package/dist/providers/codex-responses-codec.d.ts +0 -43
- package/dist/providers/codex-responses-codec.js +0 -497
- package/dist/providers/google-backend.d.ts +0 -11
- package/dist/providers/google-backend.js +0 -48
- package/dist/providers/google-codec.d.ts +0 -28
- package/dist/providers/google-codec.js +0 -312
- package/dist/providers/openai-backend.d.ts +0 -41
- package/dist/providers/openai-backend.js +0 -176
- package/dist/providers/openai-reasoning.d.ts +0 -9
- package/dist/providers/openai-reasoning.js +0 -29
- package/dist/providers/protocol.d.ts +0 -56
- package/dist/providers/protocol.js +0 -368
- package/dist/providers/source.d.ts +0 -19
- package/dist/providers/source.js +0 -81
- package/dist/providers/types.d.ts +0 -44
- package/dist/providers/types.js +0 -2
- package/dist/sse/chat-assembler.d.ts +0 -42
- package/dist/sse/chat-assembler.js +0 -248
- package/dist/sse/parse.d.ts +0 -2
- package/dist/sse/parse.js +0 -1
- package/dist/test/bedrock-source.test.js +0 -786
- /package/dist/test/{bedrock-source.test.d.ts → cluster-gateway.test.d.ts} +0 -0
package/dist/acp/registry.js
CHANGED
|
@@ -10,7 +10,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
10
10
|
import { join, resolve } from "node:path";
|
|
11
11
|
import { executeWebRequest, RouteKitFailure, routeKitError, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
|
|
12
12
|
import { Effect } from "effect";
|
|
13
|
-
import { gatewayTry } from "
|
|
13
|
+
import { gatewayTry } from "@velum-labs/routekit-provider-core";
|
|
14
14
|
export const ACP_REGISTRY_URL = "https://cdn.agentclientprotocol.com/registry/v1/latest/registry.json";
|
|
15
15
|
function normalizeRegistry(raw) {
|
|
16
16
|
if (typeof raw !== "object" || raw === null) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Streaming translation lives in `anthropic-stream.ts`; HTTP handling lives
|
|
5
5
|
* in `anthropic.ts`.
|
|
6
6
|
*/
|
|
7
|
-
import type { OpenAiChatResponse } from "
|
|
7
|
+
import type { OpenAiChatResponse } from "@velum-labs/routekit-provider-core";
|
|
8
8
|
import type { AnthropicRequest } from "./anthropic-wire.js";
|
|
9
9
|
import type { ExecutedSearch, ServerToolLoopEvent } from "./server-tool-loop.js";
|
|
10
10
|
export type { AnthropicRequest } from "./anthropic-wire.js";
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* in `anthropic.ts`.
|
|
6
6
|
*/
|
|
7
7
|
import { estimateTokens, randomId } from "@velum-labs/routekit-runtime/timing";
|
|
8
|
-
import { decodeToolResult } from "
|
|
9
|
-
import { droppedField } from "
|
|
10
|
-
import { anthropicReasoningDetailsOf, anthropicReasoningExtension, attachAnthropicMessageContent, attachAnthropicRequestMetadata, attachReasoningSelection, attachReasoningSelectionError, reasoningIndex } from "
|
|
8
|
+
import { decodeToolResult } from "@velum-labs/routekit-provider-core";
|
|
9
|
+
import { droppedField } from "@velum-labs/routekit-provider-core";
|
|
10
|
+
import { anthropicReasoningDetailsOf, anthropicReasoningExtension, attachAnthropicMessageContent, attachAnthropicRequestMetadata, attachReasoningSelection, attachReasoningSelectionError, reasoningIndex } from "@velum-labs/routekit-provider-core";
|
|
11
11
|
/**
|
|
12
12
|
* Whether an Anthropic tool is *server-executed* (run by Anthropic's backend,
|
|
13
13
|
* e.g. `web_search_20250305` / `code_execution_*`). Nothing behind this gateway
|
|
@@ -329,9 +329,20 @@ export function anthropicToChat(body, backendModel, options = {}) {
|
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
}
|
|
332
|
+
// Claude Code can retain its global effort preference when the process
|
|
333
|
+
// disables thinking. Disabled thinking wins; forwarding the stale effort
|
|
334
|
+
// would make the canonical request metadata contradictory.
|
|
335
|
+
const outputConfig = body.thinking?.type === "disabled" && body.output_config != null
|
|
336
|
+
? Object.fromEntries(Object.entries(body.output_config).filter(([key]) => key !== "effort"))
|
|
337
|
+
: body.output_config;
|
|
332
338
|
const metadata = {
|
|
339
|
+
...(body.service_tier !== undefined ? { service_tier: body.service_tier } : {}),
|
|
340
|
+
...(body.speed !== undefined ? { speed: body.speed } : {}),
|
|
333
341
|
...(body.thinking != null ? { thinking: body.thinking } : {}),
|
|
334
|
-
...(
|
|
342
|
+
...(outputConfig !== undefined &&
|
|
343
|
+
(outputConfig === null || Object.keys(outputConfig).length > 0)
|
|
344
|
+
? { output_config: outputConfig }
|
|
345
|
+
: {})
|
|
335
346
|
};
|
|
336
347
|
if (Object.keys(metadata).length > 0) {
|
|
337
348
|
attachAnthropicRequestMetadata(chat, metadata);
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { randomId } from "@velum-labs/routekit-runtime/timing";
|
|
6
6
|
import { StreamPump } from "@velum-labs/routekit-runtime/sse";
|
|
7
|
-
import { decodeOpenAiChatSseEvent } from "
|
|
8
|
-
import { SseParseError } from "
|
|
7
|
+
import { decodeOpenAiChatSseEvent } from "@velum-labs/routekit-provider-core";
|
|
8
|
+
import { SseParseError } from "@velum-labs/routekit-provider-core";
|
|
9
9
|
import { mapStopReason } from "./anthropic-codec.js";
|
|
10
|
-
import { anthropicReasoningDetailsOf, anthropicReasoningExtension } from "
|
|
10
|
+
import { anthropicReasoningDetailsOf, anthropicReasoningExtension } from "@velum-labs/routekit-provider-core";
|
|
11
11
|
import { serverToolMarkerOf } from "./server-tool-loop.js";
|
|
12
12
|
import { unwrapUpstreamError } from "./upstream-error.js";
|
|
13
13
|
const ENCODER = new TextEncoder();
|
|
@@ -37,7 +37,9 @@ export function openAiSseToAnthropic(upstream, model) {
|
|
|
37
37
|
nextIndex: 0,
|
|
38
38
|
finished: false,
|
|
39
39
|
inputTokens: undefined,
|
|
40
|
-
outputTokens: undefined
|
|
40
|
+
outputTokens: undefined,
|
|
41
|
+
stopReason: undefined,
|
|
42
|
+
stopSequence: null
|
|
41
43
|
};
|
|
42
44
|
const ensureStarted = (controller) => {
|
|
43
45
|
if (state.started)
|
|
@@ -292,7 +294,7 @@ export function openAiSseToAnthropic(upstream, model) {
|
|
|
292
294
|
return;
|
|
293
295
|
}
|
|
294
296
|
const choice = chunk.choices?.[0];
|
|
295
|
-
if (choice === undefined) {
|
|
297
|
+
if (choice === undefined || state.stopReason !== undefined) {
|
|
296
298
|
if (chunk.usage?.inputTokens !== undefined)
|
|
297
299
|
state.inputTokens = chunk.usage.inputTokens;
|
|
298
300
|
if (chunk.usage?.outputTokens !== undefined)
|
|
@@ -387,18 +389,28 @@ export function openAiSseToAnthropic(upstream, model) {
|
|
|
387
389
|
if (chunk.usage?.outputTokens !== undefined)
|
|
388
390
|
state.outputTokens = chunk.usage.outputTokens;
|
|
389
391
|
if (choice.finish_reason !== null && choice.finish_reason !== undefined) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
392
|
+
// OpenAI sends the usage-only chunk *after* the finish_reason when
|
|
393
|
+
// stream_options.include_usage is enabled. Defer the terminal Anthropic
|
|
394
|
+
// event so both the client and call accounting receive those counters.
|
|
395
|
+
state.stopReason =
|
|
396
|
+
typeof choice.anthropic_stop_reason === "string"
|
|
397
|
+
? choice.anthropic_stop_reason
|
|
398
|
+
: mapStopReason(choice.finish_reason);
|
|
399
|
+
state.stopSequence =
|
|
400
|
+
typeof choice.anthropic_stop_sequence === "string" ? choice.anthropic_stop_sequence : null;
|
|
393
401
|
}
|
|
394
402
|
};
|
|
395
403
|
const handleEvent = (controller, data) => {
|
|
396
|
-
if (data.length === 0)
|
|
404
|
+
if (data.length === 0 || state.finished)
|
|
397
405
|
return;
|
|
398
406
|
if (data === "[DONE]") {
|
|
399
|
-
|
|
400
|
-
|
|
407
|
+
if (state.stopReason === undefined) {
|
|
408
|
+
// A `[DONE]` without a prior finish_reason is truncation, not a clean stop.
|
|
401
409
|
finalizeTruncated(controller, "upstream sent [DONE] before a finish reason");
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
finalize(controller, state.stopReason, state.stopSequence);
|
|
413
|
+
}
|
|
402
414
|
return;
|
|
403
415
|
}
|
|
404
416
|
let chunk;
|
|
@@ -437,7 +449,12 @@ export function openAiSseToAnthropic(upstream, model) {
|
|
|
437
449
|
},
|
|
438
450
|
onEnd(controller) {
|
|
439
451
|
if (!state.finished) {
|
|
440
|
-
|
|
452
|
+
if (state.stopReason === undefined) {
|
|
453
|
+
finalizeTruncated(controller, "upstream stream ended before a finish reason");
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
finalize(controller, state.stopReason, state.stopSequence);
|
|
457
|
+
}
|
|
441
458
|
}
|
|
442
459
|
}
|
|
443
460
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* clients send to `/v1/messages`. Translation lives in `anthropic-codec.ts`;
|
|
4
4
|
* HTTP handling lives in `anthropic.ts`.
|
|
5
5
|
*/
|
|
6
|
-
import type { AnthropicThinkingConfig } from "
|
|
6
|
+
import type { AnthropicThinkingConfig } from "@velum-labs/routekit-provider-core";
|
|
7
7
|
export type AnthropicTextBlock = {
|
|
8
8
|
type: "text";
|
|
9
9
|
text: string;
|
|
@@ -54,6 +54,10 @@ export type AnthropicRequest = {
|
|
|
54
54
|
temperature?: number;
|
|
55
55
|
top_p?: number;
|
|
56
56
|
top_k?: number;
|
|
57
|
+
/** Anthropic capacity routing (`auto` or `standard_only`), not fast mode. */
|
|
58
|
+
service_tier?: string | null;
|
|
59
|
+
/** Anthropic fast-mode selector (`fast`); omitted means normal mode. */
|
|
60
|
+
speed?: string | null;
|
|
57
61
|
thinking?: AnthropicThinking | null;
|
|
58
62
|
output_config?: AnthropicOutputConfig | null;
|
|
59
63
|
metadata?: Record<string, unknown> | null;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - `anthropic-models.ts` — Claude picker and `/v1/models`
|
|
13
13
|
* - this module — HTTP handlers that wire the codec to a `Backend`
|
|
14
14
|
*/
|
|
15
|
-
import type { Backend, BackendRequest, BackendRequestOptions } from "
|
|
15
|
+
import type { Backend, BackendRequest, BackendRequestOptions } from "@velum-labs/routekit-provider-core";
|
|
16
16
|
import type { AnthropicRequest } from "./anthropic-wire.js";
|
|
17
17
|
export type { AnthropicTranslationOptions } from "./anthropic-codec.js";
|
|
18
18
|
export { anthropicToChat, chatToAnthropicMessage, countTokensEstimate, mapStopReason } from "./anthropic-codec.js";
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
* - `anthropic-models.ts` — Claude picker and `/v1/models`
|
|
13
13
|
* - this module — HTTP handlers that wire the codec to a `Backend`
|
|
14
14
|
*/
|
|
15
|
+
import { decodeOpenAiChatResponse, gatewayTry, gatewayTryPromise, jsonResponse } from "@velum-labs/routekit-provider-core";
|
|
16
|
+
import { requireHttpSubmissionSupport } from "@velum-labs/routekit-runtime/effect";
|
|
15
17
|
import { Effect } from "effect";
|
|
16
|
-
import { gatewayTry, gatewayTryPromise } from "../effect/gateway.js";
|
|
17
|
-
import { jsonResponse } from "../http/response.js";
|
|
18
|
-
import { decodeOpenAiChatResponse } from "../providers/protocol.js";
|
|
19
18
|
import { anthropicToChat, chatToAnthropicMessage, countTokensEstimate, isAnthropicServerTool, isAnthropicWebSearchTool, thinkingValidationError, WEB_SEARCH_TOOL_NAME } from "./anthropic-codec.js";
|
|
20
19
|
import { openAiSseToAnthropic } from "./anthropic-stream.js";
|
|
21
20
|
import { composeServerToolStream, runBufferedServerToolLoop } from "./server-tool-loop.js";
|
|
@@ -63,6 +62,9 @@ export function handleAnthropicMessages(backend, body, modelCallId, signal, back
|
|
|
63
62
|
const declaresWebSearch = body.tools?.some(isAnthropicWebSearchTool) === true;
|
|
64
63
|
const clientNameCollision = body.tools?.some((tool) => !isAnthropicServerTool(tool) && tool.name === WEB_SEARCH_TOOL_NAME) === true;
|
|
65
64
|
const executor = declaresWebSearch && !clientNameCollision ? resolveWebSearchExecutor("anthropic") : undefined;
|
|
65
|
+
// Native provider support does not certify the server-tool loop's
|
|
66
|
+
// additional provider POSTs or its detached streaming runner.
|
|
67
|
+
yield* requireHttpSubmissionSupport(executor === undefined);
|
|
66
68
|
const serverTools = executor !== undefined;
|
|
67
69
|
const chat = anthropicToChat(body, upstreamModel, { serverTools });
|
|
68
70
|
const requestOptions = {
|
package/dist/adapters/cursor.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* boundary stays defensive without 4xx-ing on new shapes.
|
|
16
16
|
*/
|
|
17
17
|
import { cursorModelName, EFFORT_QUALIFIED_MODEL_CODEC, enumerateModelEffortVariants, resolveModelEffortVariant, stripCursorNamespace } from "@velum-labs/routekit-contracts";
|
|
18
|
-
import { droppedField } from "
|
|
19
|
-
import { attachReasoningSelection, attachReasoningSelectionError, hasExplicitReasoningSelection, reasoningSelectionErrorOf, reasoningSelectionOf } from "
|
|
18
|
+
import { droppedField } from "@velum-labs/routekit-provider-core";
|
|
19
|
+
import { attachReasoningSelection, attachReasoningSelectionError, hasExplicitReasoningSelection, reasoningSelectionErrorOf, reasoningSelectionOf } from "@velum-labs/routekit-provider-core";
|
|
20
20
|
/** Fields copied through unchanged when present and non-null. */
|
|
21
21
|
const PASSTHROUGH_FIELDS = [
|
|
22
22
|
"model",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* translation lives in `responses-stream.ts`; HTTP handling lives in
|
|
5
5
|
* `responses.ts`.
|
|
6
6
|
*/
|
|
7
|
-
import type { OpenAiChatResponse } from "
|
|
7
|
+
import type { OpenAiChatResponse } from "@velum-labs/routekit-provider-core";
|
|
8
8
|
import type { ResponsesRequest, ResponsesTool } from "./responses-wire.js";
|
|
9
9
|
import type { ExecutedSearch } from "./server-tool-loop.js";
|
|
10
10
|
export type { ResponsesInputItem, ResponsesRequest } from "./responses-wire.js";
|
|
@@ -62,4 +62,15 @@ export type ResponsesTranslationOptions = {
|
|
|
62
62
|
export declare function responsesToolRegistry(body: ResponsesRequest, options?: ResponsesTranslationOptions): ResponsesToolRegistry;
|
|
63
63
|
/** Translate a Responses request to an OpenAI Chat Completions body. */
|
|
64
64
|
export declare function responsesToChat(body: ResponsesRequest, backendModel: string | undefined, options?: ResponsesTranslationOptions): Record<string, unknown>;
|
|
65
|
+
/** The native item for a typed tool call (e.g. name "tool_search" ->
|
|
66
|
+
* `tool_search_call`). Codex dispatches typed tools by payload shape; a
|
|
67
|
+
* `function_call` under the same name fails with "unsupported payload". */
|
|
68
|
+
export declare function typedToolCallItem(input: {
|
|
69
|
+
name: string;
|
|
70
|
+
itemId: string;
|
|
71
|
+
callId: string;
|
|
72
|
+
args: string;
|
|
73
|
+
}): Record<string, unknown>;
|
|
74
|
+
/** The `query` from a web_search call's JSON arguments (raw args as fallback). */
|
|
75
|
+
export declare function webSearchQueryOf(args: string): string;
|
|
65
76
|
export declare function chatToResponses(openai: OpenAiResponse, model: string, toolRegistry?: ResponsesToolRegistry, searches?: readonly ExecutedSearch[]): Record<string, unknown>;
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* `responses.ts`.
|
|
6
6
|
*/
|
|
7
7
|
import { randomId } from "@velum-labs/routekit-runtime/timing";
|
|
8
|
-
import { droppedField } from "
|
|
9
|
-
import { attachReasoningSelection, attachReasoningSelectionError, attachResponsesReasoningMetadata, hasExplicitReasoningSelection, reasoningSelectionErrorOf, reasoningSelectionOf, responsesReasoningItem, responsesReasoningMetadataOf } from "
|
|
8
|
+
import { droppedField } from "@velum-labs/routekit-provider-core";
|
|
9
|
+
import { attachReasoningSelection, attachReasoningSelectionError, attachResponsesReasoningMetadata, hasExplicitReasoningSelection, reasoningSelectionErrorOf, reasoningSelectionOf, responsesReasoningItem, responsesReasoningMetadataOf } from "@velum-labs/routekit-provider-core";
|
|
10
10
|
import { chatUsageToResponses } from "./responses-usage.js";
|
|
11
11
|
export class ResponsesTranslationError extends Error {
|
|
12
12
|
code = "invalid_encrypted_reasoning_order";
|
|
@@ -53,11 +53,33 @@ function contentToText(content) {
|
|
|
53
53
|
return content;
|
|
54
54
|
return content.map(partText).join("");
|
|
55
55
|
}
|
|
56
|
-
function contentToParts(content) {
|
|
56
|
+
function contentToParts(content, destinationWireShape) {
|
|
57
57
|
if (typeof content === "string")
|
|
58
58
|
return content;
|
|
59
59
|
const parts = [];
|
|
60
60
|
for (const part of content) {
|
|
61
|
+
if (destinationWireShape === "openai-responses") {
|
|
62
|
+
// The Codex Chat bridge validates content before transport. Do not
|
|
63
|
+
// erase unsupported modalities before that codec can reject them.
|
|
64
|
+
if (part?.type === "input_image") {
|
|
65
|
+
parts.push({
|
|
66
|
+
type: "image_url",
|
|
67
|
+
image_url: {
|
|
68
|
+
url: part.image_url,
|
|
69
|
+
...(part.detail !== undefined ? { detail: part.detail } : {})
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (part?.type === "input_text" ||
|
|
74
|
+
part?.type === "output_text" ||
|
|
75
|
+
part?.type === "text") {
|
|
76
|
+
parts.push({ type: "text", text: part.text });
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
parts.push(part);
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
61
83
|
if (part.type === "input_image" && typeof part.image_url === "string") {
|
|
62
84
|
parts.push({ type: "image_url", image_url: { url: part.image_url } });
|
|
63
85
|
}
|
|
@@ -70,8 +92,8 @@ function contentToParts(content) {
|
|
|
70
92
|
parts.push({ type: "text", text });
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
|
-
if (parts.length === 1 && parts[0]?.type === "text") {
|
|
74
|
-
return
|
|
95
|
+
if (parts.length === 1 && parts[0]?.type === "text" && typeof parts[0].text === "string") {
|
|
96
|
+
return parts[0].text;
|
|
75
97
|
}
|
|
76
98
|
return parts;
|
|
77
99
|
}
|
|
@@ -400,7 +422,11 @@ export function responsesToChat(body, backendModel, options = {}) {
|
|
|
400
422
|
if (pendingEncryptedReasoning.length > 0)
|
|
401
423
|
rejectPendingReasoning(item.type);
|
|
402
424
|
const out = item;
|
|
403
|
-
const content =
|
|
425
|
+
const content = options.destinationWireShape === "openai-responses" && Array.isArray(out.output)
|
|
426
|
+
? contentToParts(out.output, options.destinationWireShape)
|
|
427
|
+
: typeof out.output === "string"
|
|
428
|
+
? out.output
|
|
429
|
+
: JSON.stringify(out.output);
|
|
404
430
|
messages.push({ role: "tool", tool_call_id: out.call_id, content });
|
|
405
431
|
continue;
|
|
406
432
|
}
|
|
@@ -430,7 +456,7 @@ export function responsesToChat(body, backendModel, options = {}) {
|
|
|
430
456
|
}
|
|
431
457
|
const chatMessage = {
|
|
432
458
|
role,
|
|
433
|
-
content: contentToParts(message.content)
|
|
459
|
+
content: contentToParts(message.content, options.destinationWireShape)
|
|
434
460
|
};
|
|
435
461
|
if (role === "assistant")
|
|
436
462
|
attachPendingReasoning(chatMessage);
|
|
@@ -455,6 +481,8 @@ export function responsesToChat(body, backendModel, options = {}) {
|
|
|
455
481
|
chat.top_p = body.top_p;
|
|
456
482
|
if (typeof body.parallel_tool_calls === "boolean")
|
|
457
483
|
chat.parallel_tool_calls = body.parallel_tool_calls;
|
|
484
|
+
if (typeof body.service_tier === "string")
|
|
485
|
+
chat.service_tier = body.service_tier;
|
|
458
486
|
if (body.x_routekit !== undefined)
|
|
459
487
|
chat.x_routekit = body.x_routekit;
|
|
460
488
|
const originalSelectionError = reasoningSelectionErrorOf(body);
|
|
@@ -627,7 +655,7 @@ function typedToolArguments(args) {
|
|
|
627
655
|
/** The native item for a typed tool call (e.g. name "tool_search" ->
|
|
628
656
|
* `tool_search_call`). Codex dispatches typed tools by payload shape; a
|
|
629
657
|
* `function_call` under the same name fails with "unsupported payload". */
|
|
630
|
-
function typedToolCallItem(input) {
|
|
658
|
+
export function typedToolCallItem(input) {
|
|
631
659
|
return {
|
|
632
660
|
type: `${input.name}_call`,
|
|
633
661
|
id: input.itemId,
|
|
@@ -638,7 +666,7 @@ function typedToolCallItem(input) {
|
|
|
638
666
|
};
|
|
639
667
|
}
|
|
640
668
|
/** The `query` from a web_search call's JSON arguments (raw args as fallback). */
|
|
641
|
-
function webSearchQueryOf(args) {
|
|
669
|
+
export function webSearchQueryOf(args) {
|
|
642
670
|
try {
|
|
643
671
|
const parsed = JSON.parse(args);
|
|
644
672
|
if (typeof parsed.query === "string")
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ResponsesToolRegistry } from "./responses-codec.js";
|
|
2
2
|
export declare function openAiSseToResponses(upstream: ReadableStream<Uint8Array>, model: string, toolRegistry?: ResponsesToolRegistry): ReadableStream<Uint8Array>;
|
|
@@ -1,32 +1,13 @@
|
|
|
1
1
|
import { extensionValue } from "@velum-labs/routekit-contracts/protocol-ir";
|
|
2
2
|
import { randomId } from "@velum-labs/routekit-runtime/timing";
|
|
3
3
|
import { StreamPump } from "@velum-labs/routekit-runtime/sse";
|
|
4
|
-
import { decodeOpenAiChatSseEvent } from "
|
|
5
|
-
import { SseParseError } from "
|
|
6
|
-
import { responsesReasoningItem, responsesReasoningMetadataOf } from "
|
|
4
|
+
import { decodeOpenAiChatSseEvent } from "@velum-labs/routekit-provider-core";
|
|
5
|
+
import { SseParseError } from "@velum-labs/routekit-provider-core";
|
|
6
|
+
import { responsesReasoningItem, responsesReasoningMetadataOf } from "@velum-labs/routekit-provider-core";
|
|
7
|
+
import { typedToolCallItem, webSearchQueryOf } from "./responses-codec.js";
|
|
7
8
|
import { chatUsageToResponses } from "./responses-usage.js";
|
|
8
9
|
import { serverToolMarkerOf } from "./server-tool-loop.js";
|
|
9
10
|
const ENCODER = new TextEncoder();
|
|
10
|
-
function typedToolArguments(args) {
|
|
11
|
-
if (args.trim().length === 0)
|
|
12
|
-
return {};
|
|
13
|
-
try {
|
|
14
|
-
return JSON.parse(args);
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return {};
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function typedToolCallItem(input) {
|
|
21
|
-
return {
|
|
22
|
-
type: `${input.name}_call`,
|
|
23
|
-
id: input.itemId,
|
|
24
|
-
call_id: input.callId,
|
|
25
|
-
status: "completed",
|
|
26
|
-
execution: "client",
|
|
27
|
-
arguments: typedToolArguments(input.args)
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
11
|
function customToolInput(args) {
|
|
31
12
|
if (args.trim().length === 0)
|
|
32
13
|
return "";
|
|
@@ -86,18 +67,6 @@ function streamedToolItem(tool) {
|
|
|
86
67
|
}
|
|
87
68
|
}
|
|
88
69
|
}
|
|
89
|
-
/** The `query` from a web_search call's JSON arguments (raw args as fallback). */
|
|
90
|
-
function webSearchQueryOf(args) {
|
|
91
|
-
try {
|
|
92
|
-
const parsed = JSON.parse(args);
|
|
93
|
-
if (typeof parsed.query === "string")
|
|
94
|
-
return parsed.query;
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
// fall through to the raw argument string
|
|
98
|
-
}
|
|
99
|
-
return args;
|
|
100
|
-
}
|
|
101
70
|
export function openAiSseToResponses(upstream, model, toolRegistry = new Map()) {
|
|
102
71
|
const responseId = `resp_${randomId()}`;
|
|
103
72
|
const messageItemId = `msg_${randomId()}`;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* send to `/v1/responses`. Translation lives in `responses-codec.ts`; HTTP
|
|
4
4
|
* handling lives in `responses.ts`.
|
|
5
5
|
*/
|
|
6
|
-
import type { RouteKitReasoningEnvelope } from "
|
|
6
|
+
import type { RouteKitReasoningEnvelope } from "@velum-labs/routekit-provider-core";
|
|
7
7
|
export type ResponsesContentPart = {
|
|
8
8
|
type: string;
|
|
9
9
|
text?: string;
|
|
@@ -83,6 +83,7 @@ export type ResponsesRequest = {
|
|
|
83
83
|
temperature?: number;
|
|
84
84
|
top_p?: number;
|
|
85
85
|
parallel_tool_calls?: boolean;
|
|
86
|
+
service_tier?: string | null;
|
|
86
87
|
/**
|
|
87
88
|
* Codex serializes `reasoning: null` (not an absent key) for models whose
|
|
88
89
|
* catalog metadata carries no reasoning level — every custom provider
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - `responses-usage.ts` — usage mapping
|
|
13
13
|
* - this module — HTTP handlers that wire the codec to a `Backend`
|
|
14
14
|
*/
|
|
15
|
-
import type { Backend, BackendRequest, BackendRequestOptions } from "
|
|
15
|
+
import type { Backend, BackendRequest, BackendRequestOptions } from "@velum-labs/routekit-provider-core";
|
|
16
16
|
import type { ResponsesRequest } from "./responses-wire.js";
|
|
17
17
|
export type { ResponsesToolEntry, ResponsesToolKind, ResponsesToolRegistry, ResponsesTranslationOptions } from "./responses-codec.js";
|
|
18
18
|
export { chatToResponses, responsesToChat, responsesToolRegistry, WEB_SEARCH_TOOL_NAME } from "./responses-codec.js";
|
|
@@ -12,14 +12,9 @@
|
|
|
12
12
|
* - `responses-usage.ts` — usage mapping
|
|
13
13
|
* - this module — HTTP handlers that wire the codec to a `Backend`
|
|
14
14
|
*/
|
|
15
|
-
import {
|
|
15
|
+
import { decodeOpenAiChatResponse, droppedField, finalizeResponsesResponse, gatewayTry, gatewayTryPromise, jsonResponse, prepareResponsesReasoningInput, reasoningSelectionOf, routeKitRequestValidationErrorOf } from "@velum-labs/routekit-provider-core";
|
|
16
|
+
import { requireHttpSubmissionSupport, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
|
|
16
17
|
import { Effect } from "effect";
|
|
17
|
-
import { gatewayTry, gatewayTryPromise } from "../effect/gateway.js";
|
|
18
|
-
import { jsonResponse } from "../http/response.js";
|
|
19
|
-
import { decodeOpenAiChatResponse } from "../providers/protocol.js";
|
|
20
|
-
import { droppedField } from "./dropped.js";
|
|
21
|
-
import { reasoningSelectionOf, routeKitRequestValidationErrorOf } from "./openai-chat-wire.js";
|
|
22
|
-
import { prepareResponsesReasoningInput, wrapResponsesReasoningResponse } from "./openai-responses-wire.js";
|
|
23
18
|
import { chatToResponses, isServerWebSearchTool, ResponsesTranslationError, responsesToChat, responsesToolRegistry, WEB_SEARCH_TOOL_NAME } from "./responses-codec.js";
|
|
24
19
|
import { openAiSseToResponses } from "./responses-stream.js";
|
|
25
20
|
import { composeServerToolStream, runBufferedServerToolLoop } from "./server-tool-loop.js";
|
|
@@ -126,7 +121,9 @@ export function handleResponses(backend, body, modelCallId, signal, backendOptio
|
|
|
126
121
|
...backendOptions,
|
|
127
122
|
modelCallId
|
|
128
123
|
});
|
|
129
|
-
return yield*
|
|
124
|
+
return yield* finalizeResponsesResponse(response, reasoningOwner, {
|
|
125
|
+
stream: body.stream === true
|
|
126
|
+
});
|
|
130
127
|
}
|
|
131
128
|
// Server-executed web search is honored when the caller declared the tool,
|
|
132
129
|
// an executor is available (a provider key exists), and no *client* tool
|
|
@@ -135,6 +132,10 @@ export function handleResponses(backend, body, modelCallId, signal, backendOptio
|
|
|
135
132
|
const declaresWebSearch = body.tools?.some(isServerWebSearchTool) === true;
|
|
136
133
|
const clientNameCollision = body.tools?.some((tool) => tool.name === WEB_SEARCH_TOOL_NAME) === true;
|
|
137
134
|
const executor = declaresWebSearch && !clientNameCollision ? resolveWebSearchExecutor("responses") : undefined;
|
|
135
|
+
// The server-tool owner has additional provider POSTs and an async
|
|
136
|
+
// streaming runner that do not yet carry strict request authority.
|
|
137
|
+
// Refuse the compound operation before its first model submission.
|
|
138
|
+
yield* requireHttpSubmissionSupport(executor === undefined);
|
|
138
139
|
const serverTools = executor !== undefined;
|
|
139
140
|
const toolRegistry = responsesToolRegistry(body, { serverTools });
|
|
140
141
|
const includeCompatibleBody = requestsEncryptedReasoning && !preservesOpaqueReasoning
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
import { type Reasoning, type ToolResult } from "@velum-labs/routekit-contracts/protocol-ir";
|
|
27
27
|
import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
|
|
28
28
|
import { type Context, Effect } from "effect";
|
|
29
|
-
import type { BackendRequest } from "
|
|
30
|
-
import { type OpenAiChatResponse } from "
|
|
29
|
+
import type { BackendRequest } from "@velum-labs/routekit-provider-core";
|
|
30
|
+
import { type OpenAiChatResponse } from "@velum-labs/routekit-provider-core";
|
|
31
31
|
import type { WebSearchExecutor } from "./web-search.js";
|
|
32
32
|
/** In-process marker chunk field the loop injects between composed steps. */
|
|
33
33
|
export declare const SERVER_TOOL_MARKER_FIELD = "routekit_server_tool";
|
|
@@ -28,11 +28,11 @@ import { randomId } from "@velum-labs/routekit-runtime/timing";
|
|
|
28
28
|
import { RouteKitFailure, runRouteKitEffect, runRouteKitEffectWith } from "@velum-labs/routekit-runtime/effect";
|
|
29
29
|
import { StreamPump } from "@velum-labs/routekit-runtime/sse";
|
|
30
30
|
import { Effect } from "effect";
|
|
31
|
-
import { gatewayTry, gatewayTryPromise } from "
|
|
32
|
-
import { decodeOpenAiChatResponse, decodeOpenAiChatSseEvent, decodeOpenAiToolCalls, isProviderRecord } from "
|
|
33
|
-
import { ChatStreamAssembler } from "
|
|
34
|
-
import { SseParseError } from "
|
|
35
|
-
import { googleToolCallIndexesOf, responsesReasoningMetadataOf } from "
|
|
31
|
+
import { gatewayTry, gatewayTryPromise } from "@velum-labs/routekit-provider-core";
|
|
32
|
+
import { decodeOpenAiChatResponse, decodeOpenAiChatSseEvent, decodeOpenAiToolCalls, isProviderRecord } from "@velum-labs/routekit-provider-core";
|
|
33
|
+
import { ChatStreamAssembler } from "@velum-labs/routekit-provider-core";
|
|
34
|
+
import { SseParseError } from "@velum-labs/routekit-provider-core";
|
|
35
|
+
import { googleToolCallIndexesOf, responsesReasoningMetadataOf } from "@velum-labs/routekit-provider-core";
|
|
36
36
|
import { canonicalServerToolReasoning, nativeAnthropicServerToolReasoning, serverToolAssistantMessage } from "./server-tool-transcript.js";
|
|
37
37
|
import { MAX_WEB_SEARCHES_PER_TURN } from "./web-search.js";
|
|
38
38
|
const ENCODER = new TextEncoder();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* the synthetic tool turn. Loop orchestration lives in `server-tool-loop.ts`.
|
|
5
5
|
*/
|
|
6
6
|
import type { Reasoning } from "@velum-labs/routekit-contracts/protocol-ir";
|
|
7
|
-
import { type ResponsesReasoningState } from "
|
|
7
|
+
import { type ResponsesReasoningState } from "@velum-labs/routekit-provider-core";
|
|
8
8
|
export type ServerToolTranscriptCall = {
|
|
9
9
|
providerIndex?: number;
|
|
10
10
|
id?: string;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* the synthetic tool turn. Loop orchestration lives in `server-tool-loop.ts`.
|
|
5
5
|
*/
|
|
6
6
|
import { randomId } from "@velum-labs/routekit-runtime/timing";
|
|
7
|
-
import { ANTHROPIC_MESSAGE_CONTENT, anthropicReasoningDetailsOf, anthropicReasoningExtension, attachGoogleToolCallIndexes, attachResponsesReasoningMetadata, googleThoughtDetailsOf, reasoningIndex } from "
|
|
7
|
+
import { ANTHROPIC_MESSAGE_CONTENT, anthropicReasoningDetailsOf, anthropicReasoningExtension, attachGoogleToolCallIndexes, attachResponsesReasoningMetadata, googleThoughtDetailsOf, reasoningIndex } from "@velum-labs/routekit-provider-core";
|
|
8
8
|
export function canonicalServerToolReasoning(details) {
|
|
9
9
|
return [
|
|
10
10
|
...anthropicReasoningDetailsOf(details, "message"),
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type RouteKitPlatform } from "@velum-labs/routekit-runtime/effect";
|
|
2
|
+
import { Context, Effect, Scope } from "effect";
|
|
3
|
+
/** Own the runtime created for a published, standalone listener. */
|
|
4
|
+
export declare function startStandaloneListener<I, A extends {
|
|
5
|
+
readonly close: Effect.Effect<void, unknown>;
|
|
6
|
+
}>(service: Context.Key<I, A>, start: Effect.Effect<A, Error, RouteKitPlatform | Scope.Scope>): Promise<A>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { RouteKitLive, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
|
|
2
|
+
import { Effect, Layer, ManagedRuntime } from "effect";
|
|
3
|
+
/** Own the runtime created for a published, standalone listener. */
|
|
4
|
+
export async function startStandaloneListener(service, start) {
|
|
5
|
+
const layer = Layer.effect(service, Effect.acquireRelease(start, (listener) => listener.close.pipe(Effect.ignore))).pipe(Layer.provide(RouteKitLive));
|
|
6
|
+
const runtime = ManagedRuntime.make(layer);
|
|
7
|
+
try {
|
|
8
|
+
const listener = await runtime.runPromise(service);
|
|
9
|
+
let closeRun;
|
|
10
|
+
const disposeRuntime = Effect.tryPromise({
|
|
11
|
+
try: () => {
|
|
12
|
+
closeRun ??= runtime.dispose();
|
|
13
|
+
return closeRun;
|
|
14
|
+
},
|
|
15
|
+
catch: toRouteKitFailure
|
|
16
|
+
}).pipe(Effect.ignore);
|
|
17
|
+
return {
|
|
18
|
+
...listener,
|
|
19
|
+
close: listener.close.pipe(Effect.ensuring(disposeRuntime))
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
await runtime.dispose().catch(() => undefined);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { withDeadline } from "@velum-labs/routekit-runtime/timing";
|
|
18
18
|
import { executeWebRequest, RouteKitFailure, routeKitError, toRouteKitFailure } from "@velum-labs/routekit-runtime/effect";
|
|
19
19
|
import { Effect } from "effect";
|
|
20
|
-
import { decodeAnthropicWebSearchResult, decodeOpenAiWebSearchResult } from "
|
|
20
|
+
import { decodeAnthropicWebSearchResult, decodeOpenAiWebSearchResult } from "@velum-labs/routekit-provider-core";
|
|
21
21
|
/** Hard ceiling on gateway-executed searches within one caller turn. */
|
|
22
22
|
export const MAX_WEB_SEARCHES_PER_TURN = 8;
|
|
23
23
|
/** Per-search wall clock before the delegated call is aborted. */
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Prefix the first non-empty Anthropic public text block or text delta. */
|
|
2
|
+
export declare function decorateAnthropicStream(body: ReadableStream<Uint8Array>, prefix: string): ReadableStream<Uint8Array>;
|
|
3
|
+
/** Decorate a buffered Anthropic Messages response without adding a content block. */
|
|
4
|
+
export declare function decorateAnthropicJson(value: unknown, prefix: string): boolean;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { prefixOnce, recordOf, transformJsonSse } from "./sse-transform.js";
|
|
2
|
+
/** Prefix the first non-empty Anthropic public text block or text delta. */
|
|
3
|
+
export function decorateAnthropicStream(body, prefix) {
|
|
4
|
+
let emitted = false;
|
|
5
|
+
return transformJsonSse(body, (value) => {
|
|
6
|
+
if (emitted)
|
|
7
|
+
return undefined;
|
|
8
|
+
const event = recordOf(value);
|
|
9
|
+
if (event?.type === "content_block_start") {
|
|
10
|
+
const block = recordOf(event.content_block);
|
|
11
|
+
if (block?.type === "text" && typeof block.text === "string" && block.text.length > 0) {
|
|
12
|
+
block.text = prefixOnce(prefix, block.text);
|
|
13
|
+
emitted = true;
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (event?.type === "content_block_delta") {
|
|
18
|
+
const delta = recordOf(event.delta);
|
|
19
|
+
if (delta?.type === "text_delta" &&
|
|
20
|
+
typeof delta.text === "string" &&
|
|
21
|
+
delta.text.length > 0) {
|
|
22
|
+
delta.text = prefixOnce(prefix, delta.text);
|
|
23
|
+
emitted = true;
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Decorate a buffered Anthropic Messages response without adding a content block. */
|
|
31
|
+
export function decorateAnthropicJson(value, prefix) {
|
|
32
|
+
const content = recordOf(value)?.content;
|
|
33
|
+
if (!Array.isArray(content))
|
|
34
|
+
return false;
|
|
35
|
+
for (const part of content) {
|
|
36
|
+
const block = recordOf(part);
|
|
37
|
+
if (block?.type === "text" &&
|
|
38
|
+
typeof block.text === "string" &&
|
|
39
|
+
block.text.length > 0) {
|
|
40
|
+
block.text = prefixOnce(prefix, block.text);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Prefix the first non-empty Chat Completions public content delta. */
|
|
2
|
+
export declare function decorateChatStream(body: ReadableStream<Uint8Array>, prefix: string): ReadableStream<Uint8Array>;
|
|
3
|
+
/** Decorate a buffered Chat Completions response without creating a choice or message. */
|
|
4
|
+
export declare function decorateChatJson(value: unknown, prefix: string): boolean;
|