@deepstrike/sdk 0.2.52 → 0.2.60

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.
Files changed (148) hide show
  1. package/README.md +28 -28
  2. package/dist/agent-ir.d.ts +103 -0
  3. package/dist/agent-ir.js +134 -0
  4. package/dist/agent.d.ts +67 -0
  5. package/dist/agent.js +36 -0
  6. package/dist/collaboration/harness.js +1 -1
  7. package/dist/collaboration/modes/creator-verifier.d.ts +2 -7
  8. package/dist/collaboration/modes/creator-verifier.js +4 -6
  9. package/dist/collaboration/pool.d.ts +8 -20
  10. package/dist/collaboration/pool.js +27 -97
  11. package/dist/compat/anthropic/mcp.d.ts +15 -0
  12. package/dist/compat/anthropic/mcp.js +10 -0
  13. package/dist/compat/openai/agent.d.ts +34 -0
  14. package/dist/compat/openai/agent.js +24 -0
  15. package/dist/governance.d.ts +1 -17
  16. package/dist/governance.js +1 -34
  17. package/dist/guardrail.d.ts +6 -0
  18. package/dist/guardrail.js +1 -0
  19. package/dist/handoff-target.d.ts +12 -0
  20. package/dist/handoff-target.js +1 -0
  21. package/dist/harness/manifest.js +0 -4
  22. package/dist/index.d.ts +19 -7
  23. package/dist/index.js +8 -5
  24. package/dist/kernel.d.ts +2 -20
  25. package/dist/knowledge/public.d.ts +29 -0
  26. package/dist/knowledge/public.js +1 -0
  27. package/dist/mcp-server.d.ts +28 -0
  28. package/dist/mcp-server.js +1 -0
  29. package/dist/memory/agent.d.ts +2 -2
  30. package/dist/memory/agent.js +2 -2
  31. package/dist/memory/durable.d.ts +16 -0
  32. package/dist/memory/durable.js +46 -0
  33. package/dist/memory/in-memory-store.d.ts +9 -7
  34. package/dist/memory/in-memory-store.js +8 -2
  35. package/dist/memory/protocols.d.ts +22 -4
  36. package/dist/memory/public.d.ts +4 -3
  37. package/dist/memory/public.js +3 -2
  38. package/dist/providers/anthropic-adapter.d.ts +47 -0
  39. package/dist/providers/anthropic-adapter.js +444 -0
  40. package/dist/providers/anthropic-compatible.d.ts +2 -3
  41. package/dist/providers/anthropic-compatible.js +8 -5
  42. package/dist/providers/anthropic.d.ts +19 -23
  43. package/dist/providers/anthropic.js +169 -395
  44. package/dist/providers/base.js +43 -3
  45. package/dist/providers/capability-router.d.ts +29 -0
  46. package/dist/providers/capability-router.js +43 -0
  47. package/dist/providers/catalog.d.ts +16 -4
  48. package/dist/providers/catalog.js +112 -21
  49. package/dist/providers/content-normalization.d.ts +57 -0
  50. package/dist/providers/content-normalization.js +238 -0
  51. package/dist/providers/content-policy.d.ts +16 -0
  52. package/dist/providers/content-policy.js +39 -0
  53. package/dist/providers/credentials.d.ts +83 -0
  54. package/dist/providers/credentials.js +190 -0
  55. package/dist/providers/endpoints.d.ts +137 -0
  56. package/dist/providers/endpoints.js +128 -0
  57. package/dist/providers/factories.js +9 -3
  58. package/dist/providers/gemini-adapter.d.ts +33 -0
  59. package/dist/providers/gemini-adapter.js +272 -0
  60. package/dist/providers/gemini.d.ts +16 -3
  61. package/dist/providers/gemini.js +97 -195
  62. package/dist/providers/model-catalog.d.ts +37 -0
  63. package/dist/providers/model-catalog.js +62 -0
  64. package/dist/providers/model-registry.d.ts +98 -0
  65. package/dist/providers/model-registry.js +315 -0
  66. package/dist/providers/ollama-adapter.d.ts +65 -0
  67. package/dist/providers/ollama-adapter.js +188 -0
  68. package/dist/providers/ollama.d.ts +9 -4
  69. package/dist/providers/ollama.js +96 -109
  70. package/dist/providers/openai-chat-dialects.d.ts +154 -0
  71. package/dist/providers/openai-chat-dialects.js +179 -0
  72. package/dist/providers/openai-chat.d.ts +46 -18
  73. package/dist/providers/openai-chat.js +418 -51
  74. package/dist/providers/openai-responses-adapter.d.ts +42 -0
  75. package/dist/providers/openai-responses-adapter.js +341 -0
  76. package/dist/providers/openai-responses.d.ts +14 -33
  77. package/dist/providers/openai-responses.js +131 -264
  78. package/dist/providers/openai.d.ts +29 -76
  79. package/dist/providers/openai.js +195 -292
  80. package/dist/providers/protocol-adapter.d.ts +34 -0
  81. package/dist/providers/protocol-adapter.js +9 -0
  82. package/dist/providers/protocol-capabilities.d.ts +34 -0
  83. package/dist/providers/protocol-capabilities.js +44 -0
  84. package/dist/providers/provider-error.d.ts +31 -0
  85. package/dist/providers/provider-error.js +149 -0
  86. package/dist/providers/public.d.ts +26 -3
  87. package/dist/providers/public.js +13 -1
  88. package/dist/providers/registry.d.ts +7 -6
  89. package/dist/providers/registry.js +47 -20
  90. package/dist/providers/request-plan.d.ts +86 -0
  91. package/dist/providers/request-plan.js +175 -0
  92. package/dist/providers/usage-normalizer.d.ts +48 -0
  93. package/dist/providers/usage-normalizer.js +96 -0
  94. package/dist/providers/vendor-profiles.d.ts +2 -15
  95. package/dist/providers/vendor-profiles.js +14 -60
  96. package/dist/runtime/canonical-kernel-step.d.ts +1 -2
  97. package/dist/runtime/canonical-kernel-step.js +47 -12
  98. package/dist/runtime/context-policy.d.ts +10 -12
  99. package/dist/runtime/context-policy.js +6 -8
  100. package/dist/runtime/durable-content.d.ts +50 -0
  101. package/dist/runtime/durable-content.js +159 -0
  102. package/dist/runtime/execution-plane.d.ts +2 -2
  103. package/dist/runtime/execution-plane.js +2 -2
  104. package/dist/runtime/kernel-event-log.js +0 -1
  105. package/dist/runtime/kernel-step.d.ts +0 -1
  106. package/dist/runtime/kernel-step.js +4 -2
  107. package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
  108. package/dist/runtime/mcp-proxy-plane.js +44 -6
  109. package/dist/runtime/output-schema.d.ts +1 -2
  110. package/dist/runtime/provider-replay.d.ts +1 -1
  111. package/dist/runtime/provider-replay.js +12 -27
  112. package/dist/runtime/reactive-session.d.ts +1 -1
  113. package/dist/runtime/reactive-session.js +2 -3
  114. package/dist/runtime/run-group.d.ts +1 -1
  115. package/dist/runtime/runner.d.ts +26 -40
  116. package/dist/runtime/runner.js +145 -63
  117. package/dist/runtime/session-log.d.ts +8 -1
  118. package/dist/runtime/session-log.js +42 -2
  119. package/dist/runtime/session-repair.d.ts +1 -1
  120. package/dist/runtime/session-repair.js +1 -1
  121. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  122. package/dist/runtime/sub-agent-orchestrator.js +8 -11
  123. package/dist/runtime/workflow-control-flow.d.ts +0 -4
  124. package/dist/runtime/workflow-control-flow.js +0 -16
  125. package/dist/session.d.ts +11 -0
  126. package/dist/session.js +1 -0
  127. package/dist/skill.d.ts +17 -0
  128. package/dist/skill.js +16 -0
  129. package/dist/skills/loader.d.ts +3 -0
  130. package/dist/tools/errors.d.ts +1 -3
  131. package/dist/tools/errors.js +1 -3
  132. package/dist/tools/index.d.ts +3 -0
  133. package/dist/types/agent.d.ts +21 -9
  134. package/dist/types/agent.js +30 -4
  135. package/dist/types.d.ts +128 -13
  136. package/package.json +3 -3
  137. package/dist/providers/deepseek.d.ts +0 -46
  138. package/dist/providers/deepseek.js +0 -97
  139. package/dist/providers/glm.d.ts +0 -25
  140. package/dist/providers/glm.js +0 -48
  141. package/dist/providers/kimi.d.ts +0 -23
  142. package/dist/providers/kimi.js +0 -30
  143. package/dist/providers/minimax.d.ts +0 -49
  144. package/dist/providers/minimax.js +0 -98
  145. package/dist/providers/profiles.d.ts +0 -1992
  146. package/dist/providers/profiles.js +0 -796
  147. package/dist/providers/qwen.d.ts +0 -38
  148. package/dist/providers/qwen.js +0 -97
@@ -1,3 +1,4 @@
1
+ import { normalizeToolResultPart, projectToolOutputToText } from "./content-normalization.js";
1
2
  export class CircuitBreaker {
2
3
  openAfter;
3
4
  resetAfter;
@@ -140,11 +141,40 @@ export function toAnthropicContent(msg) {
140
141
  throw new UnsupportedModalityError("audio", "anthropic");
141
142
  }
142
143
  if (p.type === "tool_result") {
143
- return { type: "tool_result", tool_use_id: p.callId, content: p.output, is_error: p.isError };
144
+ return { type: "tool_result", tool_use_id: p.callId, content: toolResultAnthropicContent(p), is_error: p.isError };
144
145
  }
145
146
  return { type: "text", text: "" };
146
147
  });
147
148
  }
149
+ /**
150
+ * spc_012-N-03: a `ContentBlock` inside a structured tool result → Anthropic wire block.
151
+ * Anthropic's `tool_result.content` natively accepts text and image blocks. Anything else
152
+ * (audio/video/file, `fileId`/`object` sources with no Anthropic wire form) degrades to an
153
+ * explicit `[modality]` placeholder visible to the model, never silently dropped (INV-012-01).
154
+ */
155
+ function contentBlockToAnthropic(block) {
156
+ if (block.type === "text")
157
+ return { type: "text", text: block.text };
158
+ if (block.type === "image") {
159
+ const src = block.source;
160
+ if (src.kind === "base64") {
161
+ return { type: "image", source: { type: "base64", media_type: block.mediaType ?? "image/png", data: src.data } };
162
+ }
163
+ if (src.kind === "url") {
164
+ return { type: "image", source: { type: "url", url: src.url } };
165
+ }
166
+ return { type: "text", text: "[image]" };
167
+ }
168
+ return { type: "text", text: `[${block.type}]` };
169
+ }
170
+ /**
171
+ * Structured `contentParts` win when present; otherwise `output` is the text form.
172
+ */
173
+ function toolResultAnthropicContent(p) {
174
+ if (p.contentParts !== undefined)
175
+ return normalizeToolResultPart(p).blocks.map(contentBlockToAnthropic);
176
+ return p.output;
177
+ }
148
178
  /**
149
179
  * History turns with the volatile State turn appended as the latest turn, for
150
180
  * providers that render it inline (OpenAI-family, Gemini, Ollama). Appending
@@ -166,7 +196,7 @@ export function toAnthropicMessages(turns, nativeReplay) {
166
196
  if (msg.role === "tool") {
167
197
  const parts = (msg.contentParts ?? [])
168
198
  .filter((p) => p.type === "tool_result")
169
- .map(p => ({ type: "tool_result", tool_use_id: p.callId, content: p.output, is_error: p.isError }));
199
+ .map(p => ({ type: "tool_result", tool_use_id: p.callId, content: toolResultAnthropicContent(p), is_error: p.isError }));
170
200
  if (parts.length)
171
201
  result.push({ role: "user", content: parts });
172
202
  continue;
@@ -246,10 +276,20 @@ export function toOpenAIMessageParams(context) {
246
276
  // bindings, where the state is already inside `turns`.
247
277
  for (const msg of turnsWithStateAppended(context)) {
248
278
  if (msg.role === "tool") {
279
+ // spc_012-N-04: OpenAI **chat completions** tool-role messages accept text only — no
280
+ // structured tool_result content exists on this wire. Explicit text-only degradation:
281
+ // `output` is the constructor's text projection, which carries a visible `[modality]`
282
+ // placeholder for any structured block (INV-012-01 — degraded, not silently dropped).
283
+ // The Responses API (openai-responses.ts) does have native structured support and uses it.
249
284
  const parts = (msg.contentParts ?? [])
250
285
  .filter((p) => p.type === "tool_result");
251
286
  for (const p of parts) {
252
- result.push({ role: "tool", tool_call_id: p.callId, content: p.output });
287
+ const canonical = normalizeToolResultPart(p);
288
+ result.push({
289
+ role: "tool",
290
+ tool_call_id: p.callId,
291
+ content: projectToolOutputToText(canonical.blocks),
292
+ });
253
293
  }
254
294
  continue;
255
295
  }
@@ -0,0 +1,29 @@
1
+ import type { CreateProviderOptions } from "./catalog.js";
2
+ import type { InputModality, ResolvedProviderRuntime } from "./model-registry.js";
3
+ export interface CapabilityRequirement {
4
+ requiredInputModalities?: readonly InputModality[];
5
+ tools?: boolean;
6
+ reasoning?: boolean;
7
+ minimumContextWindow?: number;
8
+ }
9
+ export type CapabilityRouteResult = {
10
+ ok: true;
11
+ runtime: ResolvedProviderRuntime;
12
+ } | {
13
+ ok: false;
14
+ error: {
15
+ code: "no_capable_model";
16
+ requirement: CapabilityRequirement;
17
+ candidates: Array<{
18
+ model: string;
19
+ rejectedBy: string[];
20
+ }>;
21
+ };
22
+ };
23
+ /**
24
+ * Deliberately policy-free first-match router. Unknown capability evidence stays eligible for
25
+ * forward compatibility; only evidence of unsupported capability rejects a candidate.
26
+ */
27
+ export declare class CapabilityRouter {
28
+ route(requirement: CapabilityRequirement, candidates: readonly CreateProviderOptions[]): Promise<CapabilityRouteResult>;
29
+ }
@@ -0,0 +1,43 @@
1
+ import { resolveProviderRuntimeAsync } from "./catalog.js";
2
+ /**
3
+ * Deliberately policy-free first-match router. Unknown capability evidence stays eligible for
4
+ * forward compatibility; only evidence of unsupported capability rejects a candidate.
5
+ */
6
+ export class CapabilityRouter {
7
+ async route(requirement, candidates) {
8
+ const rejected = [];
9
+ for (const candidate of candidates) {
10
+ let runtime;
11
+ try {
12
+ runtime = await resolveProviderRuntimeAsync(candidate);
13
+ }
14
+ catch {
15
+ rejected.push({ model: candidate.model, rejectedBy: ["unavailable"] });
16
+ continue;
17
+ }
18
+ const rejectedBy = rejects(requirement, runtime);
19
+ if (rejectedBy.length === 0)
20
+ return { ok: true, runtime };
21
+ rejected.push({ model: candidate.model, rejectedBy });
22
+ }
23
+ return { ok: false, error: { code: "no_capable_model", requirement, candidates: rejected } };
24
+ }
25
+ }
26
+ function rejects(requirement, runtime) {
27
+ const result = [];
28
+ for (const modality of requirement.requiredInputModalities ?? []) {
29
+ if (runtime.effectiveCapabilities.inputModalities[modality].state === "unsupported") {
30
+ result.push(`input:${modality}`);
31
+ }
32
+ }
33
+ if (requirement.tools && runtime.effectiveCapabilities.tools.state === "unsupported")
34
+ result.push("tools");
35
+ if (requirement.reasoning && runtime.effectiveCapabilities.reasoning.state === "unsupported")
36
+ result.push("reasoning");
37
+ if (requirement.minimumContextWindow !== undefined
38
+ && runtime.model.contextWindow !== undefined
39
+ && runtime.model.contextWindow < requirement.minimumContextWindow) {
40
+ result.push("context");
41
+ }
42
+ return result;
43
+ }
@@ -1,9 +1,17 @@
1
1
  import type { LLMProvider } from "../types.js";
2
- import { endpointProfiles, type ModelProfileId, type ProviderId } from "./profiles.js";
3
- export type EndpointProfileId = keyof typeof endpointProfiles;
2
+ import { type ResolvedProviderRuntime } from "./model-registry.js";
3
+ import { type EndpointProfileId, type ProviderId } from "./endpoints.js";
4
+ import { type CredentialOptions } from "./credentials.js";
5
+ import type { ModelCatalog } from "./model-catalog.js";
6
+ export type { EndpointProfileId } from "./endpoints.js";
4
7
  export interface CreateProviderOptions {
5
- model: ModelProfileId | string;
6
- apiKey: string;
8
+ model: string;
9
+ /** Explicit credential; resolver-backed construction can omit it. */
10
+ apiKey?: string;
11
+ bearerToken?: string;
12
+ credentialResolver?: CredentialOptions["credentialResolver"];
13
+ /** Host-provided catalog used only for lookup; it never carries routing policy. */
14
+ modelCatalog?: ModelCatalog;
7
15
  provider?: ProviderId;
8
16
  endpoint?: EndpointProfileId;
9
17
  retry?: {
@@ -13,3 +21,7 @@ export interface CreateProviderOptions {
13
21
  baseURL?: string;
14
22
  }
15
23
  export declare function createProvider(options: CreateProviderOptions): LLMProvider;
24
+ export declare function resolveProviderRuntime(options: CreateProviderOptions): ResolvedProviderRuntime;
25
+ /** I/O-capable equivalent for host credential resolvers and dynamic catalogs. */
26
+ export declare function resolveProviderRuntimeAsync(options: CreateProviderOptions): Promise<ResolvedProviderRuntime>;
27
+ export declare function createProviderAsync(options: CreateProviderOptions): Promise<LLMProvider>;
@@ -1,9 +1,44 @@
1
- import { PROVIDER_REGISTRY, providerRegistryKey } from "./registry.js";
2
- import { endpointProfiles, getModelProfile, modelProfiles } from "./profiles.js";
1
+ import { PROVIDER_REGISTRY, providerRegistryKey, supportsBearerCredential } from "./registry.js";
2
+ import { endpointCapabilitiesFor, generationProtocol, modelRegistry, normalizeModelId, resolveEffectiveModelCapabilities, } from "./model-registry.js";
3
+ import { endpointProfiles } from "./endpoints.js";
4
+ import { resolveCredential, resolveCredentialSync, CredentialResolutionError, } from "./credentials.js";
3
5
  export function createProvider(options) {
4
- const profile = isModelProfileId(options.model) ? getModelProfile(options.model) : undefined;
6
+ return resolveProviderRuntime(options).adapter;
7
+ }
8
+ export function resolveProviderRuntime(options) {
9
+ const draft = resolveRuntimeDraft(options);
10
+ const credential = draft.providerId === "ollama"
11
+ ? { type: "api_key", value: "" }
12
+ : resolveCredentialSync(draft.credentialRequest, options);
13
+ return constructResolvedRuntime(draft, credential, options);
14
+ }
15
+ /** I/O-capable equivalent for host credential resolvers and dynamic catalogs. */
16
+ export async function resolveProviderRuntimeAsync(options) {
17
+ const draft = await resolveRuntimeDraftAsync(options);
18
+ const credential = draft.providerId === "ollama"
19
+ ? { type: "api_key", value: "" }
20
+ : await resolveCredential(draft.credentialRequest, options);
21
+ return constructResolvedRuntime(draft, credential, options);
22
+ }
23
+ export async function createProviderAsync(options) {
24
+ return (await resolveProviderRuntimeAsync(options)).adapter;
25
+ }
26
+ function resolveRuntimeDraft(options) {
5
27
  const parsedProviderId = providerPrefix(options.model);
6
- const endpointId = (options.endpoint ?? profile?.defaultEndpointId ?? defaultEndpointForProvider(options.provider ?? parsedProviderId));
28
+ const providerHint = options.provider ?? parsedProviderId;
29
+ const initialRegistration = modelRegistry.resolve(options.model, providerHint);
30
+ return resolveRuntimeDraftWithRegistration(options, parsedProviderId, initialRegistration);
31
+ }
32
+ async function resolveRuntimeDraftAsync(options) {
33
+ const parsedProviderId = providerPrefix(options.model);
34
+ const providerHint = options.provider ?? parsedProviderId;
35
+ const fromCatalog = options.modelCatalog ? await options.modelCatalog.get(options.model) : undefined;
36
+ const initialRegistration = fromCatalog ?? modelRegistry.resolve(options.model, providerHint);
37
+ return resolveRuntimeDraftWithRegistration(options, parsedProviderId, initialRegistration);
38
+ }
39
+ function resolveRuntimeDraftWithRegistration(options, parsedProviderId, initialRegistration) {
40
+ const providerHint = options.provider ?? parsedProviderId;
41
+ const endpointId = (options.endpoint ?? initialRegistration?.defaultEndpointId ?? defaultEndpointForProvider(providerHint));
7
42
  if (!endpointId) {
8
43
  throw new Error(`Unknown model profile: ${options.model}. Pass provider or endpoint for custom model names.`);
9
44
  }
@@ -11,26 +46,84 @@ export function createProvider(options) {
11
46
  if (!endpoint) {
12
47
  throw new Error(`Unknown endpoint profile: ${endpointId}`);
13
48
  }
14
- const providerId = profile?.providerId ?? options.provider ?? parsedProviderId ?? endpoint.providerId;
15
- if (profile && options.provider && options.provider !== profile.providerId) {
16
- throw new Error(`Model ${profile.id} belongs to provider ${profile.providerId}, not ${options.provider}`);
17
- }
49
+ const providerId = (initialRegistration?.descriptor.providerId ?? options.provider ?? parsedProviderId ?? endpoint.providerId);
50
+ const registration = initialRegistration ?? modelRegistry.resolve(options.model, providerId);
51
+ if (!registration)
52
+ throw new Error(`Unable to resolve model ${options.model} for provider ${providerId}`);
18
53
  if (parsedProviderId && options.provider && parsedProviderId !== options.provider) {
19
54
  throw new Error(`Model ${options.model} uses provider prefix ${parsedProviderId}, not ${options.provider}`);
20
55
  }
21
56
  if (endpoint.providerId !== providerId) {
22
57
  throw new Error(`Endpoint ${endpoint.id} does not belong to provider ${providerId}`);
23
58
  }
24
- const model = modelNameForProvider(options.model, providerId);
25
- const baseURL = options.baseURL ?? endpoint.baseURL;
26
- // Single data-driven dispatch: one registry keyed by (providerId, protocol).
27
- const make = PROVIDER_REGISTRY[providerRegistryKey(providerId, endpoint.protocol)];
28
- if (make)
29
- return make(options.apiKey, model, options.retry, baseURL);
30
- throw new Error(`No Node provider factory for ${options.model} on ${endpoint.id}`);
59
+ const model = normalizeModelId(providerId, options.model);
60
+ return {
61
+ providerId,
62
+ model,
63
+ endpointId,
64
+ endpoint,
65
+ registration,
66
+ credentialRequest: { providerId, modelId: model, endpointId, protocol: endpoint.protocol },
67
+ };
68
+ }
69
+ function constructResolvedRuntime(draft, credential, options) {
70
+ if (credential.type === "bearer" && !supportsBearerCredential(draft.providerId, draft.endpoint.protocol)) {
71
+ throw new CredentialResolutionError("credential_auth_mode_unsupported", draft.providerId);
72
+ }
73
+ const make = PROVIDER_REGISTRY[providerRegistryKey(draft.providerId, draft.endpoint.protocol)];
74
+ if (!make)
75
+ throw new Error(`No Node provider factory for ${draft.model} on ${draft.endpoint.id}`);
76
+ const protocol = generationProtocol(draft.endpoint.protocol);
77
+ if (!protocol)
78
+ throw new Error(`No Node provider factory for ${draft.model} on ${draft.endpoint.id}`);
79
+ const adapter = make(credential.value, draft.model, options.retry, options.baseURL ?? draft.endpoint.baseURL, draft.registration.recommendedRuntimePolicy, credential.type);
80
+ const resolved = {
81
+ identity: { providerId: draft.providerId, modelId: draft.model, endpointId: draft.endpointId, protocol },
82
+ model: draft.registration.descriptor,
83
+ endpoint: draft.endpoint,
84
+ adapter,
85
+ effectiveCapabilities: resolveEffectiveModelCapabilities({
86
+ model: draft.registration.descriptor,
87
+ protocol,
88
+ endpointCapabilities: endpointCapabilitiesFor(draft.endpointId, options.baseURL === undefined || options.endpoint !== undefined),
89
+ }),
90
+ ...(draft.registration.recommendedRuntimePolicy ? { runtimePolicy: draft.registration.recommendedRuntimePolicy } : {}),
91
+ };
92
+ const bind = adapter.bindResolvedRuntime;
93
+ bind?.call(adapter, resolved);
94
+ const requestPlanIdentity = {
95
+ providerId: resolved.identity.providerId,
96
+ modelId: resolved.identity.modelId,
97
+ endpoint: {
98
+ id: resolved.identity.endpointId,
99
+ protocol: resolved.identity.protocol,
100
+ baseURL: safeEndpointBaseURL(options.baseURL ?? draft.endpoint.baseURL),
101
+ },
102
+ ...(resolved.effectiveCapabilities.nativeTokenCounting.state === "supported"
103
+ && resolved.effectiveCapabilities.nativeTokenCounting.value === true
104
+ ? { nativeTokenCounting: true }
105
+ : {}),
106
+ };
107
+ adapter
108
+ .requestPlanIdentity = () => ({
109
+ ...requestPlanIdentity,
110
+ endpoint: { ...requestPlanIdentity.endpoint },
111
+ });
112
+ return resolved;
31
113
  }
32
- function isModelProfileId(model) {
33
- return Object.prototype.hasOwnProperty.call(modelProfiles, model);
114
+ function safeEndpointBaseURL(baseURL) {
115
+ try {
116
+ const parsed = new URL(baseURL);
117
+ parsed.username = "";
118
+ parsed.password = "";
119
+ parsed.search = "";
120
+ parsed.hash = "";
121
+ return parsed.toString().replace(/\/$/, "");
122
+ }
123
+ catch {
124
+ // Provider construction has already accepted this opaque endpoint. Do not invent a replacement.
125
+ return baseURL;
126
+ }
34
127
  }
35
128
  function providerPrefix(model) {
36
129
  const [prefix] = model.split("/", 1);
@@ -51,10 +144,8 @@ function defaultEndpointForProvider(providerId) {
51
144
  qwen: "qwen.anthropic",
52
145
  gemini: "gemini.google",
53
146
  glm: "glm.anthropic",
147
+ baai: "baai.self-hosted.embeddings",
148
+ ollama: "ollama.local",
54
149
  };
55
150
  return defaults[providerId];
56
151
  }
57
- function modelNameForProvider(model, providerId) {
58
- const prefix = `${providerId}/`;
59
- return model.startsWith(prefix) ? model.slice(prefix.length) : model;
60
- }
@@ -0,0 +1,57 @@
1
+ import type { Message, RenderedContext, ToolOutputBlock, ToolResultPart, ToolSchema, ToolCall, ProviderReplay } from "../types.js";
2
+ import type { ResolvedProviderRuntime } from "./model-registry.js";
3
+ export declare class ContentValidationError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ export declare class ToolResultProjectionConflictError extends ContentValidationError {
7
+ constructor(callId: string);
8
+ }
9
+ export interface CanonicalToolResult {
10
+ readonly type: "tool_result";
11
+ readonly callId: string;
12
+ readonly blocks: readonly ToolOutputBlock[];
13
+ readonly isError: boolean;
14
+ /** Preserves the caller's explicit text-vs-block wire intent without duplicating content. */
15
+ readonly contentForm?: "text" | "blocks";
16
+ }
17
+ export type CanonicalMessageBlock = ToolOutputBlock | CanonicalToolResult;
18
+ export interface CanonicalMessage {
19
+ readonly role: Message["role"];
20
+ readonly blocks: readonly CanonicalMessageBlock[];
21
+ /** `blocks` remains authoritative; this only preserves an observable wire-shape distinction. */
22
+ readonly contentForm?: "text" | "blocks";
23
+ readonly toolCalls?: readonly ToolCall[];
24
+ readonly tokenCount?: number;
25
+ readonly providerReplay?: ProviderReplay;
26
+ }
27
+ export interface CanonicalRenderedContext {
28
+ readonly systemText: string;
29
+ readonly systemStable?: string;
30
+ readonly systemKnowledge?: string;
31
+ readonly turns: readonly CanonicalMessage[];
32
+ readonly stateTurn?: CanonicalMessage;
33
+ readonly frozenPrefixLen?: number;
34
+ readonly budgetOverflow?: RenderedContext["budgetOverflow"];
35
+ }
36
+ export interface CanonicalAdapterInput {
37
+ readonly context: CanonicalRenderedContext;
38
+ readonly tools: readonly ToolSchema[];
39
+ readonly resolved: ResolvedProviderRuntime<unknown>;
40
+ readonly extensions: Readonly<Record<string, unknown>>;
41
+ }
42
+ export declare function validateToolOutputBlocks(blocks: readonly ToolOutputBlock[]): void;
43
+ export declare function projectToolOutputToText(blocks: readonly ToolOutputBlock[]): string;
44
+ export declare function normalizeToolResultPart(part: ToolResultPart): CanonicalToolResult;
45
+ /** Attach process-local blocks only within the operation that produced them. */
46
+ export declare function attachToolOutputOverlay(context: RenderedContext, overlay: ReadonlyMap<string, readonly ToolOutputBlock[]>): RenderedContext;
47
+ export declare function normalizeCanonicalContext(context: RenderedContext, replayForMessage?: (message: Message) => ProviderReplay | undefined): CanonicalRenderedContext;
48
+ /** Full-tree validation uses the resolved operation capabilities. Unknown stays unknown/fail-open. */
49
+ export declare function validateCanonicalAdapterInput(input: CanonicalAdapterInput): void;
50
+ export declare function normalizeCanonicalAdapterInput(input: {
51
+ context: RenderedContext;
52
+ tools: readonly ToolSchema[];
53
+ resolved: ResolvedProviderRuntime<unknown>;
54
+ extensions?: Readonly<Record<string, unknown>>;
55
+ replayForMessage?: (message: Message) => ProviderReplay | undefined;
56
+ }): CanonicalAdapterInput;
57
+ export declare function normalizeToolResultContent(callId: string, output: string, isError: boolean, contentParts: readonly ToolOutputBlock[] | undefined): CanonicalToolResult;
@@ -0,0 +1,238 @@
1
+ import { requireContentDisposition } from "./content-policy.js";
2
+ export class ContentValidationError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "ContentValidationError";
6
+ }
7
+ }
8
+ export class ToolResultProjectionConflictError extends ContentValidationError {
9
+ constructor(callId) {
10
+ super(`Tool result projection conflict for ${callId}: output does not match contentParts`);
11
+ this.name = "ToolResultProjectionConflictError";
12
+ }
13
+ }
14
+ function requireNonEmpty(value, label) {
15
+ if (typeof value !== "string" || value.length === 0) {
16
+ throw new ContentValidationError(`${label} must be a non-empty string`);
17
+ }
18
+ }
19
+ function requireBase64(value, label) {
20
+ requireNonEmpty(value, label);
21
+ if (value.length % 4 !== 0
22
+ || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
23
+ throw new ContentValidationError(`${label} is not valid base64`);
24
+ }
25
+ }
26
+ function validateSource(source, modality) {
27
+ if (!source || typeof source !== "object") {
28
+ throw new ContentValidationError(`${modality} source is required`);
29
+ }
30
+ switch (source.kind) {
31
+ case "url":
32
+ requireNonEmpty(source.url, `${modality} URL`);
33
+ return;
34
+ case "base64":
35
+ requireBase64(source.data, `${modality} base64 data`);
36
+ return;
37
+ case "fileId":
38
+ requireNonEmpty(source.id, `${modality} file id`);
39
+ return;
40
+ case "object":
41
+ requireNonEmpty(source.handle, `${modality} object handle`);
42
+ return;
43
+ default: throw new ContentValidationError(`${modality} source kind is invalid`);
44
+ }
45
+ }
46
+ export function validateToolOutputBlocks(blocks) {
47
+ for (const raw of blocks) {
48
+ if (!raw || typeof raw !== "object")
49
+ throw new ContentValidationError("tool output block must be an object");
50
+ const block = raw;
51
+ switch (block.type) {
52
+ case "text":
53
+ if (typeof block.text !== "string")
54
+ throw new ContentValidationError("tool output text must be a string");
55
+ break;
56
+ case "image":
57
+ case "audio":
58
+ case "video":
59
+ case "file":
60
+ validateSource(block.source, String(block.type));
61
+ break;
62
+ case "tool_result":
63
+ throw new ContentValidationError("nested tool_result blocks are forbidden");
64
+ default:
65
+ throw new ContentValidationError(`unknown tool output block type: ${String(block.type)}`);
66
+ }
67
+ }
68
+ }
69
+ export function projectToolOutputToText(blocks) {
70
+ validateToolOutputBlocks(blocks);
71
+ return blocks.map(block => block.type === "text" ? block.text : `[${block.type}]`).join("\n");
72
+ }
73
+ export function normalizeToolResultPart(part) {
74
+ if (part.contentParts === undefined) {
75
+ return {
76
+ type: "tool_result",
77
+ callId: part.callId,
78
+ blocks: [{ type: "text", text: part.output }],
79
+ isError: part.isError,
80
+ contentForm: "text",
81
+ };
82
+ }
83
+ const projection = projectToolOutputToText(part.contentParts);
84
+ if (projection !== part.output)
85
+ throw new ToolResultProjectionConflictError(part.callId);
86
+ return {
87
+ type: "tool_result",
88
+ callId: part.callId,
89
+ blocks: part.contentParts,
90
+ isError: part.isError,
91
+ contentForm: "blocks",
92
+ };
93
+ }
94
+ function attachMessage(message, overlay) {
95
+ const needs = message.contentParts?.some(part => part.type === "tool_result" && part.contentParts === undefined && overlay.has(part.callId));
96
+ if (!needs)
97
+ return message;
98
+ return {
99
+ ...message,
100
+ contentParts: message.contentParts.map(part => part.type === "tool_result" && part.contentParts === undefined && overlay.has(part.callId)
101
+ ? { ...part, contentParts: [...overlay.get(part.callId)] }
102
+ : part),
103
+ };
104
+ }
105
+ /** Attach process-local blocks only within the operation that produced them. */
106
+ export function attachToolOutputOverlay(context, overlay) {
107
+ if (overlay.size === 0)
108
+ return context;
109
+ const turns = context.turns.map(message => attachMessage(message, overlay));
110
+ const stateTurn = context.stateTurn ? attachMessage(context.stateTurn, overlay) : undefined;
111
+ if (turns.every((turn, index) => turn === context.turns[index]) && stateTurn === context.stateTurn)
112
+ return context;
113
+ return { ...context, turns, ...(stateTurn ? { stateTurn } : {}) };
114
+ }
115
+ function assertSupported(modality, capabilities, providerId) {
116
+ if (capabilities.inputModalities[modality].state === "unsupported") {
117
+ throw new ContentValidationError(`UnsupportedModality: ${modality} is not supported by ${providerId}`);
118
+ }
119
+ }
120
+ function validateSourceAffinity(modality, source, capabilities, resolved) {
121
+ const key = modality === "image"
122
+ ? source.kind === "url" ? "imageUrl" : source.kind === "base64" ? "imageBase64" : source.kind === "fileId" ? "fileId" : undefined
123
+ : modality === "audio"
124
+ ? source.kind === "url" ? "audioUrl" : source.kind === "base64" ? "audioBase64" : source.kind === "fileId" ? "fileId" : undefined
125
+ : source.kind === "fileId" ? "fileId" : undefined;
126
+ if (key && capabilities.mediaForms[key].state === "unsupported") {
127
+ throw new ContentValidationError(`Unsupported media source ${source.kind} for ${modality} on ${resolved.identity.providerId}`);
128
+ }
129
+ validateFileAffinity(source, resolved);
130
+ }
131
+ function validateFileAffinity(source, resolved) {
132
+ if (source.kind === "fileId" && source.affinity) {
133
+ if (source.affinity.providerId !== resolved.identity.providerId
134
+ || source.affinity.endpointId !== resolved.identity.endpointId) {
135
+ throw new ContentValidationError(`Provider file ${source.id} belongs to ${source.affinity.providerId}/${source.affinity.endpointId}, `
136
+ + `not ${resolved.identity.providerId}/${resolved.identity.endpointId}`);
137
+ }
138
+ }
139
+ }
140
+ function normalizeMessage(message, replayForMessage) {
141
+ const providerReplay = replayForMessage?.(message);
142
+ const blocks = message.contentParts === undefined
143
+ ? [{ type: "text", text: message.content }]
144
+ : message.contentParts.map(part => {
145
+ if (part.type === "text")
146
+ return { type: "text", text: part.text };
147
+ if (part.type === "image") {
148
+ if ((part.url === undefined) === (part.data === undefined)) {
149
+ throw new ContentValidationError("image requires exactly one of url or data");
150
+ }
151
+ return {
152
+ type: "image",
153
+ source: part.data !== undefined
154
+ ? { kind: "base64", data: part.data }
155
+ : { kind: "url", url: part.url },
156
+ ...(part.mediaType ? { mediaType: part.mediaType } : {}),
157
+ ...(part.detail ? { providerOptions: { openai_detail: part.detail } } : {}),
158
+ };
159
+ }
160
+ if (part.type === "audio") {
161
+ return {
162
+ type: "audio",
163
+ source: { kind: "base64", data: part.data },
164
+ mediaType: part.mediaType,
165
+ };
166
+ }
167
+ return normalizeToolResultPart(part);
168
+ });
169
+ return {
170
+ role: message.role,
171
+ blocks,
172
+ contentForm: message.contentParts === undefined ? "text" : "blocks",
173
+ ...(message.toolCalls ? { toolCalls: message.toolCalls } : {}),
174
+ ...(message.tokenCount !== undefined ? { tokenCount: message.tokenCount } : {}),
175
+ ...(providerReplay ? { providerReplay } : {}),
176
+ };
177
+ }
178
+ export function normalizeCanonicalContext(context, replayForMessage) {
179
+ return {
180
+ systemText: context.systemText,
181
+ ...(context.systemStable !== undefined ? { systemStable: context.systemStable } : {}),
182
+ ...(context.systemKnowledge !== undefined ? { systemKnowledge: context.systemKnowledge } : {}),
183
+ turns: context.turns.map(message => normalizeMessage(message, replayForMessage)),
184
+ ...(context.stateTurn ? { stateTurn: normalizeMessage(context.stateTurn, replayForMessage) } : {}),
185
+ ...(context.frozenPrefixLen !== undefined ? { frozenPrefixLen: context.frozenPrefixLen } : {}),
186
+ ...(context.budgetOverflow !== undefined ? { budgetOverflow: context.budgetOverflow } : {}),
187
+ };
188
+ }
189
+ function validateCanonicalMessage(message, resolved) {
190
+ for (const item of message.blocks) {
191
+ const placement = item.type === "tool_result" ? "tool_result" : "message";
192
+ const blocks = item.type === "tool_result" ? item.blocks : [item];
193
+ validateToolOutputBlocks(blocks);
194
+ for (const block of blocks) {
195
+ if (block.type === "text")
196
+ continue;
197
+ // Protocol policy is the stable user-visible reason for document/video refusal; a model
198
+ // capability cannot make a protocol-level unsupported shape serializable.
199
+ const disposition = requireContentDisposition(resolved.identity.protocol, block.type, placement);
200
+ // Current bridge implementations exist only for document/video tool output: their
201
+ // serializers send the deterministic visible text projection instead of the source.
202
+ // Audio and message content retain their source-form checks because their wires have
203
+ // different native constraints and no generic text bridge carrier.
204
+ if (disposition === "bridge" && placement === "tool_result" && (block.type === "file" || block.type === "video")) {
205
+ validateFileAffinity(block.source, resolved);
206
+ continue;
207
+ }
208
+ assertSupported(block.type, resolved.effectiveCapabilities, resolved.identity.providerId);
209
+ validateSourceAffinity(block.type, block.source, resolved.effectiveCapabilities, resolved);
210
+ }
211
+ }
212
+ }
213
+ /** Full-tree validation uses the resolved operation capabilities. Unknown stays unknown/fail-open. */
214
+ export function validateCanonicalAdapterInput(input) {
215
+ for (const message of input.context.turns)
216
+ validateCanonicalMessage(message, input.resolved);
217
+ if (input.context.stateTurn)
218
+ validateCanonicalMessage(input.context.stateTurn, input.resolved);
219
+ }
220
+ export function normalizeCanonicalAdapterInput(input) {
221
+ const canonical = {
222
+ context: normalizeCanonicalContext(input.context, input.replayForMessage),
223
+ tools: input.tools,
224
+ resolved: input.resolved,
225
+ extensions: input.extensions ?? {},
226
+ };
227
+ validateCanonicalAdapterInput(canonical);
228
+ return canonical;
229
+ }
230
+ export function normalizeToolResultContent(callId, output, isError, contentParts) {
231
+ return normalizeToolResultPart({
232
+ type: "tool_result",
233
+ callId,
234
+ output,
235
+ isError,
236
+ ...(contentParts === undefined ? {} : { contentParts: [...contentParts] }),
237
+ });
238
+ }
@@ -0,0 +1,16 @@
1
+ import type { InputModality } from "./protocol-capabilities.js";
2
+ export type ContentPlacement = "message" | "tool_result";
3
+ export type ContentDisposition = "native" | "bridge" | "unsupported";
4
+ /**
5
+ * Provider serialization policy for canonical content. `bridge` means the protocol has no
6
+ * structured equivalent for that placement, but serializers may send the visible text projection.
7
+ * It never permits dropping a block or replacing it with an empty string.
8
+ */
9
+ export declare function contentDispositionFor(protocol: string, modality: InputModality, placement: ContentPlacement): ContentDisposition;
10
+ export declare class ContentPolicyError extends Error {
11
+ readonly protocol: string;
12
+ readonly modality: InputModality;
13
+ readonly placement: ContentPlacement;
14
+ constructor(protocol: string, modality: InputModality, placement: ContentPlacement);
15
+ }
16
+ export declare function requireContentDisposition(protocol: string, modality: InputModality, placement: ContentPlacement): ContentDisposition;