@deepstrike/sdk 0.2.52 → 0.2.61

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 (151) 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/os/public.d.ts +1 -1
  39. package/dist/os/public.js +1 -1
  40. package/dist/providers/anthropic-adapter.d.ts +59 -0
  41. package/dist/providers/anthropic-adapter.js +530 -0
  42. package/dist/providers/anthropic-compatible.d.ts +2 -3
  43. package/dist/providers/anthropic-compatible.js +8 -5
  44. package/dist/providers/anthropic.d.ts +20 -23
  45. package/dist/providers/anthropic.js +176 -395
  46. package/dist/providers/base.d.ts +2 -2
  47. package/dist/providers/base.js +50 -8
  48. package/dist/providers/capability-router.d.ts +29 -0
  49. package/dist/providers/capability-router.js +43 -0
  50. package/dist/providers/catalog.d.ts +16 -4
  51. package/dist/providers/catalog.js +112 -36
  52. package/dist/providers/content-normalization.d.ts +57 -0
  53. package/dist/providers/content-normalization.js +238 -0
  54. package/dist/providers/content-policy.d.ts +16 -0
  55. package/dist/providers/content-policy.js +39 -0
  56. package/dist/providers/credentials.d.ts +83 -0
  57. package/dist/providers/credentials.js +190 -0
  58. package/dist/providers/endpoints.d.ts +137 -0
  59. package/dist/providers/endpoints.js +128 -0
  60. package/dist/providers/factories.js +25 -9
  61. package/dist/providers/gemini-adapter.d.ts +33 -0
  62. package/dist/providers/gemini-adapter.js +264 -0
  63. package/dist/providers/gemini.d.ts +16 -3
  64. package/dist/providers/gemini.js +97 -195
  65. package/dist/providers/model-catalog.d.ts +37 -0
  66. package/dist/providers/model-catalog.js +62 -0
  67. package/dist/providers/model-registry.d.ts +119 -0
  68. package/dist/providers/model-registry.js +379 -0
  69. package/dist/providers/ollama-adapter.d.ts +65 -0
  70. package/dist/providers/ollama-adapter.js +188 -0
  71. package/dist/providers/ollama.d.ts +9 -4
  72. package/dist/providers/ollama.js +96 -109
  73. package/dist/providers/openai-chat-dialects.d.ts +154 -0
  74. package/dist/providers/openai-chat-dialects.js +179 -0
  75. package/dist/providers/openai-chat.d.ts +46 -18
  76. package/dist/providers/openai-chat.js +416 -51
  77. package/dist/providers/openai-responses-adapter.d.ts +42 -0
  78. package/dist/providers/openai-responses-adapter.js +343 -0
  79. package/dist/providers/openai-responses.d.ts +19 -33
  80. package/dist/providers/openai-responses.js +164 -264
  81. package/dist/providers/openai.d.ts +29 -76
  82. package/dist/providers/openai.js +195 -292
  83. package/dist/providers/protocol-adapter.d.ts +39 -0
  84. package/dist/providers/protocol-adapter.js +13 -0
  85. package/dist/providers/protocol-capabilities.d.ts +34 -0
  86. package/dist/providers/protocol-capabilities.js +44 -0
  87. package/dist/providers/provider-error.d.ts +31 -0
  88. package/dist/providers/provider-error.js +153 -0
  89. package/dist/providers/public.d.ts +26 -3
  90. package/dist/providers/public.js +13 -1
  91. package/dist/providers/registry.d.ts +7 -6
  92. package/dist/providers/registry.js +47 -20
  93. package/dist/providers/request-plan.d.ts +89 -0
  94. package/dist/providers/request-plan.js +199 -0
  95. package/dist/providers/usage-normalizer.d.ts +48 -0
  96. package/dist/providers/usage-normalizer.js +139 -0
  97. package/dist/providers/vendor-profiles.d.ts +2 -15
  98. package/dist/providers/vendor-profiles.js +14 -60
  99. package/dist/runtime/canonical-kernel-step.d.ts +1 -2
  100. package/dist/runtime/canonical-kernel-step.js +47 -12
  101. package/dist/runtime/context-policy.d.ts +10 -12
  102. package/dist/runtime/context-policy.js +6 -8
  103. package/dist/runtime/durable-content.d.ts +50 -0
  104. package/dist/runtime/durable-content.js +159 -0
  105. package/dist/runtime/execution-plane.d.ts +2 -2
  106. package/dist/runtime/execution-plane.js +2 -2
  107. package/dist/runtime/kernel-event-log.js +0 -1
  108. package/dist/runtime/kernel-step.d.ts +0 -1
  109. package/dist/runtime/kernel-step.js +4 -2
  110. package/dist/runtime/mcp-proxy-plane.d.ts +25 -1
  111. package/dist/runtime/mcp-proxy-plane.js +44 -6
  112. package/dist/runtime/output-schema.d.ts +1 -2
  113. package/dist/runtime/provider-replay.d.ts +5 -1
  114. package/dist/runtime/provider-replay.js +26 -27
  115. package/dist/runtime/reactive-session.d.ts +1 -1
  116. package/dist/runtime/reactive-session.js +2 -3
  117. package/dist/runtime/run-group.d.ts +1 -1
  118. package/dist/runtime/runner.d.ts +31 -45
  119. package/dist/runtime/runner.js +178 -63
  120. package/dist/runtime/session-log.d.ts +8 -1
  121. package/dist/runtime/session-log.js +42 -2
  122. package/dist/runtime/session-repair.d.ts +1 -1
  123. package/dist/runtime/session-repair.js +1 -1
  124. package/dist/runtime/sub-agent-orchestrator.d.ts +1 -1
  125. package/dist/runtime/sub-agent-orchestrator.js +8 -11
  126. package/dist/runtime/workflow-control-flow.d.ts +0 -4
  127. package/dist/runtime/workflow-control-flow.js +0 -16
  128. package/dist/session.d.ts +11 -0
  129. package/dist/session.js +1 -0
  130. package/dist/skill.d.ts +17 -0
  131. package/dist/skill.js +16 -0
  132. package/dist/skills/loader.d.ts +3 -0
  133. package/dist/tools/errors.d.ts +1 -3
  134. package/dist/tools/errors.js +1 -3
  135. package/dist/tools/index.d.ts +3 -0
  136. package/dist/types/agent.d.ts +21 -9
  137. package/dist/types/agent.js +30 -4
  138. package/dist/types.d.ts +135 -17
  139. package/package.json +4 -4
  140. package/dist/providers/deepseek.d.ts +0 -46
  141. package/dist/providers/deepseek.js +0 -97
  142. package/dist/providers/glm.d.ts +0 -25
  143. package/dist/providers/glm.js +0 -48
  144. package/dist/providers/kimi.d.ts +0 -23
  145. package/dist/providers/kimi.js +0 -30
  146. package/dist/providers/minimax.d.ts +0 -49
  147. package/dist/providers/minimax.js +0 -98
  148. package/dist/providers/profiles.d.ts +0 -1992
  149. package/dist/providers/profiles.js +0 -796
  150. package/dist/providers/qwen.d.ts +0 -38
  151. package/dist/providers/qwen.js +0 -97
@@ -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;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Provider serialization policy for canonical content. `bridge` means the protocol has no
3
+ * structured equivalent for that placement, but serializers may send the visible text projection.
4
+ * It never permits dropping a block or replacing it with an empty string.
5
+ */
6
+ export function contentDispositionFor(protocol, modality, placement) {
7
+ if (modality === "text" || modality === "image") {
8
+ if (protocol === "ollama-chat" && modality === "image" && placement === "tool_result")
9
+ return "bridge";
10
+ return "native";
11
+ }
12
+ if (protocol === "openai-responses" && modality === "file" && placement === "message")
13
+ return "native";
14
+ if (protocol === "openai-responses" && modality === "file" && placement === "tool_result")
15
+ return "bridge";
16
+ if (protocol === "openai-chat" && ["audio", "file", "video"].includes(modality))
17
+ return "bridge";
18
+ if (protocol === "gemini" && ["audio", "file", "video"].includes(modality))
19
+ return "bridge";
20
+ return "unsupported";
21
+ }
22
+ export class ContentPolicyError extends Error {
23
+ protocol;
24
+ modality;
25
+ placement;
26
+ constructor(protocol, modality, placement) {
27
+ super(`Unsupported content policy: ${modality} ${placement} is not supported by ${protocol}`);
28
+ this.protocol = protocol;
29
+ this.modality = modality;
30
+ this.placement = placement;
31
+ this.name = "ContentPolicyError";
32
+ }
33
+ }
34
+ export function requireContentDisposition(protocol, modality, placement) {
35
+ const disposition = contentDispositionFor(protocol, modality, placement);
36
+ if (disposition === "unsupported")
37
+ throw new ContentPolicyError(protocol, modality, placement);
38
+ return disposition;
39
+ }
@@ -0,0 +1,83 @@
1
+ import type { EndpointProfileId, EndpointProtocol, ProviderId } from "./endpoints.js";
2
+ export type ProviderCredential = {
3
+ type: "api_key";
4
+ value: string;
5
+ } | {
6
+ type: "bearer";
7
+ value: string;
8
+ };
9
+ export interface CredentialRequest {
10
+ providerId: ProviderId;
11
+ modelId: string;
12
+ endpointId: EndpointProfileId;
13
+ protocol: EndpointProtocol;
14
+ }
15
+ export type CredentialResolver = (request: CredentialRequest) => ProviderCredential | undefined | Promise<ProviderCredential | undefined>;
16
+ export interface OAuthAccessToken {
17
+ accessToken: string;
18
+ /** Epoch time on the clock supplied to the resolver. The token is invalid at this instant. */
19
+ expiresAt: number;
20
+ scopes?: readonly string[];
21
+ audience?: string;
22
+ }
23
+ export interface OAuthRefreshRequest {
24
+ providerId: ProviderId;
25
+ audience?: string;
26
+ requiredScopes: readonly string[];
27
+ }
28
+ export interface OAuthCredentialResolverOptions {
29
+ providerId: ProviderId;
30
+ /** Provider-owned refresh/ADC bridge. The Host owns its transport and refresh token. */
31
+ refresh: (request: OAuthRefreshRequest) => OAuthAccessToken | Promise<OAuthAccessToken>;
32
+ requiredScopes?: readonly string[];
33
+ audience?: string;
34
+ clock?: () => number;
35
+ }
36
+ export interface CredentialOptions {
37
+ apiKey?: string;
38
+ bearerToken?: string;
39
+ credentialResolver?: CredentialResolver;
40
+ }
41
+ /** A deliberately non-sensitive credential failure. Never add resolver output to this error. */
42
+ export declare class CredentialResolutionError extends Error {
43
+ readonly code: "credential_unavailable" | "credential_invalid" | "credential_resolver_failed" | "credential_refresh_failed" | "credential_oauth_scope_mismatch" | "credential_oauth_audience_mismatch" | "credential_revoked" | "credential_auth_mode_unsupported";
44
+ readonly providerId: ProviderId;
45
+ readonly retryable: boolean;
46
+ constructor(code: CredentialResolutionError["code"], providerId: ProviderId, retryable?: boolean);
47
+ }
48
+ /**
49
+ * Host-owned OAuth/ADC extension. It retains only a refreshable bearer in process memory,
50
+ * never exposes refresh-token material, and coalesces concurrent refreshes for one provider.
51
+ */
52
+ export declare class OAuthCredentialResolver {
53
+ private readonly providerId;
54
+ private readonly refresh;
55
+ private readonly requiredScopes;
56
+ private readonly audience?;
57
+ private readonly clock;
58
+ private token?;
59
+ private refreshInFlight?;
60
+ private revoked;
61
+ constructor(options: OAuthCredentialResolverOptions);
62
+ /** An arrow field intentionally remains bound when passed as `credentialResolver`. */
63
+ readonly resolve: CredentialResolver;
64
+ revoke(): void;
65
+ status(): {
66
+ providerId: ProviderId;
67
+ revoked: boolean;
68
+ hasUsableToken: boolean;
69
+ };
70
+ private usable;
71
+ private refreshToken;
72
+ private assertUsable;
73
+ }
74
+ /** Safe for diagnostics and event metadata. It intentionally has no credential value. */
75
+ export declare function redactCredential(credential: ProviderCredential): {
76
+ type: ProviderCredential["type"];
77
+ };
78
+ export declare function resolveCredential(request: CredentialRequest, options: CredentialOptions): Promise<ProviderCredential>;
79
+ /**
80
+ * The synchronous factory remains synchronous. A resolver which performs I/O must use
81
+ * `resolveProviderRuntimeAsync` / `createProviderAsync` instead of risking an implicit request.
82
+ */
83
+ export declare function resolveCredentialSync(request: CredentialRequest, options: CredentialOptions): ProviderCredential;
@@ -0,0 +1,190 @@
1
+ /** A deliberately non-sensitive credential failure. Never add resolver output to this error. */
2
+ export class CredentialResolutionError extends Error {
3
+ code;
4
+ providerId;
5
+ retryable;
6
+ constructor(code, providerId, retryable = false) {
7
+ super(messageForCredentialError(code, providerId));
8
+ this.name = "CredentialResolutionError";
9
+ this.code = code;
10
+ this.providerId = providerId;
11
+ this.retryable = retryable;
12
+ }
13
+ }
14
+ /**
15
+ * Host-owned OAuth/ADC extension. It retains only a refreshable bearer in process memory,
16
+ * never exposes refresh-token material, and coalesces concurrent refreshes for one provider.
17
+ */
18
+ export class OAuthCredentialResolver {
19
+ providerId;
20
+ refresh;
21
+ requiredScopes;
22
+ audience;
23
+ clock;
24
+ token;
25
+ refreshInFlight;
26
+ revoked = false;
27
+ constructor(options) {
28
+ this.providerId = options.providerId;
29
+ this.refresh = options.refresh;
30
+ this.requiredScopes = [...(options.requiredScopes ?? [])];
31
+ this.audience = options.audience;
32
+ this.clock = options.clock ?? Date.now;
33
+ }
34
+ /** An arrow field intentionally remains bound when passed as `credentialResolver`. */
35
+ resolve = async (request) => {
36
+ if (request.providerId !== this.providerId)
37
+ throw unavailable(request.providerId);
38
+ if (this.revoked)
39
+ throw new CredentialResolutionError("credential_revoked", this.providerId);
40
+ const token = this.usable(this.token) ? this.token : await this.refreshToken();
41
+ this.assertUsable(token);
42
+ if (this.revoked)
43
+ throw new CredentialResolutionError("credential_revoked", this.providerId);
44
+ return { type: "bearer", value: token.accessToken };
45
+ };
46
+ revoke() {
47
+ this.revoked = true;
48
+ this.token = undefined;
49
+ }
50
+ status() {
51
+ return {
52
+ providerId: this.providerId,
53
+ revoked: this.revoked,
54
+ hasUsableToken: !this.revoked && this.usable(this.token),
55
+ };
56
+ }
57
+ usable(token) {
58
+ return token !== undefined && Number.isFinite(token.expiresAt) && token.expiresAt > this.clock();
59
+ }
60
+ async refreshToken() {
61
+ if (!this.refreshInFlight) {
62
+ this.refreshInFlight = Promise.resolve()
63
+ .then(() => this.refresh({
64
+ providerId: this.providerId,
65
+ ...(this.audience === undefined ? {} : { audience: this.audience }),
66
+ requiredScopes: this.requiredScopes,
67
+ }))
68
+ .then(token => {
69
+ this.assertUsable(token);
70
+ if (this.revoked)
71
+ throw new CredentialResolutionError("credential_revoked", this.providerId);
72
+ this.token = token;
73
+ return token;
74
+ })
75
+ .catch(error => {
76
+ if (error instanceof CredentialResolutionError)
77
+ throw error;
78
+ throw new CredentialResolutionError("credential_refresh_failed", this.providerId, true);
79
+ })
80
+ .finally(() => { this.refreshInFlight = undefined; });
81
+ }
82
+ return this.refreshInFlight;
83
+ }
84
+ assertUsable(token) {
85
+ if (!token
86
+ || typeof token.accessToken !== "string"
87
+ || token.accessToken.trim() === ""
88
+ || !Number.isFinite(token.expiresAt)
89
+ || token.expiresAt <= this.clock()) {
90
+ throw new CredentialResolutionError("credential_invalid", this.providerId);
91
+ }
92
+ if (this.audience !== undefined && token.audience !== this.audience) {
93
+ throw new CredentialResolutionError("credential_oauth_audience_mismatch", this.providerId);
94
+ }
95
+ const tokenScopes = new Set(token.scopes ?? []);
96
+ if (this.requiredScopes.some(scope => !tokenScopes.has(scope))) {
97
+ throw new CredentialResolutionError("credential_oauth_scope_mismatch", this.providerId);
98
+ }
99
+ }
100
+ }
101
+ /** Safe for diagnostics and event metadata. It intentionally has no credential value. */
102
+ export function redactCredential(credential) {
103
+ return { type: credential.type };
104
+ }
105
+ export async function resolveCredential(request, options) {
106
+ const configured = configuredCredential(request.providerId, options);
107
+ if (configured)
108
+ return configured;
109
+ if (!options.credentialResolver)
110
+ throw unavailable(request.providerId);
111
+ let resolved;
112
+ try {
113
+ resolved = await options.credentialResolver(request);
114
+ }
115
+ catch (error) {
116
+ if (error instanceof CredentialResolutionError)
117
+ throw error;
118
+ throw new CredentialResolutionError("credential_resolver_failed", request.providerId);
119
+ }
120
+ return validateCredential(resolved, request.providerId);
121
+ }
122
+ /**
123
+ * The synchronous factory remains synchronous. A resolver which performs I/O must use
124
+ * `resolveProviderRuntimeAsync` / `createProviderAsync` instead of risking an implicit request.
125
+ */
126
+ export function resolveCredentialSync(request, options) {
127
+ const configured = configuredCredential(request.providerId, options);
128
+ if (configured)
129
+ return configured;
130
+ if (!options.credentialResolver)
131
+ throw unavailable(request.providerId);
132
+ let resolved;
133
+ try {
134
+ resolved = options.credentialResolver(request);
135
+ }
136
+ catch (error) {
137
+ if (error instanceof CredentialResolutionError)
138
+ throw error;
139
+ throw new CredentialResolutionError("credential_resolver_failed", request.providerId);
140
+ }
141
+ if (isPromise(resolved)) {
142
+ throw new CredentialResolutionError("credential_resolver_failed", request.providerId);
143
+ }
144
+ return validateCredential(resolved, request.providerId);
145
+ }
146
+ function configuredCredential(providerId, options) {
147
+ const sources = [
148
+ options.apiKey === undefined ? undefined : { type: "api_key", value: options.apiKey },
149
+ options.bearerToken === undefined ? undefined : { type: "bearer", value: options.bearerToken },
150
+ ].filter((value) => value !== undefined);
151
+ if (sources.length > 1)
152
+ throw new CredentialResolutionError("credential_invalid", providerId);
153
+ return sources.length === 1 ? validateCredential(sources[0], providerId) : undefined;
154
+ }
155
+ function validateCredential(credential, providerId) {
156
+ if (!credential)
157
+ throw unavailable(providerId);
158
+ if ((credential.type !== "api_key" && credential.type !== "bearer")
159
+ || typeof credential.value !== "string"
160
+ || credential.value.trim() === "") {
161
+ throw new CredentialResolutionError("credential_invalid", providerId);
162
+ }
163
+ return credential;
164
+ }
165
+ function unavailable(providerId) {
166
+ return new CredentialResolutionError("credential_unavailable", providerId);
167
+ }
168
+ function messageForCredentialError(code, providerId) {
169
+ switch (code) {
170
+ case "credential_unavailable":
171
+ return `Missing credential for provider ${providerId}`;
172
+ case "credential_invalid":
173
+ return `Invalid credential for provider ${providerId}`;
174
+ case "credential_refresh_failed":
175
+ return `Credential refresh failed for provider ${providerId}`;
176
+ case "credential_oauth_scope_mismatch":
177
+ return `Credential scope does not satisfy provider ${providerId}`;
178
+ case "credential_oauth_audience_mismatch":
179
+ return `Credential audience does not match provider ${providerId}`;
180
+ case "credential_revoked":
181
+ return `Credential was revoked for provider ${providerId}`;
182
+ case "credential_auth_mode_unsupported":
183
+ return `Credential authentication mode is unsupported for provider ${providerId}`;
184
+ case "credential_resolver_failed":
185
+ return `Credential resolution failed for provider ${providerId}`;
186
+ }
187
+ }
188
+ function isPromise(value) {
189
+ return Boolean(value) && typeof value.then === "function";
190
+ }