@heddleagent/execution-host-client 6.7.0 → 8.0.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/README.md +54 -35
- package/dist/agentcore/agentcore-execution-host.d.ts +1 -1
- package/dist/agentcore/agentcore-execution-host.d.ts.map +1 -1
- package/dist/agentcore/agentcore-execution-host.js +6 -3
- package/dist/agentcore/agentcore-execution-host.js.map +1 -1
- package/dist/cli/hostctl.d.ts +11 -0
- package/dist/cli/hostctl.d.ts.map +1 -0
- package/dist/cli/hostctl.js +49 -0
- package/dist/cli/hostctl.js.map +1 -0
- package/dist/contracts/index.d.ts +18 -1
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +28 -1
- package/dist/contracts/index.js.map +1 -1
- package/dist/conversation/hosted-conversation-turn-service.js +2 -2
- package/dist/conversation/hosted-conversation-turn-service.js.map +1 -1
- package/dist/conversation/types.d.ts +2 -2
- package/dist/conversation/types.d.ts.map +1 -1
- package/dist/conversation/types.js.map +1 -1
- package/dist/coordinator/contracts.d.ts +148 -5
- package/dist/coordinator/contracts.d.ts.map +1 -1
- package/dist/coordinator/contracts.js +74 -3
- package/dist/coordinator/contracts.js.map +1 -1
- package/dist/coordinator/hosted-heartbeat-execution-client.d.ts +30 -0
- package/dist/coordinator/hosted-heartbeat-execution-client.d.ts.map +1 -0
- package/dist/coordinator/hosted-heartbeat-execution-client.js +240 -0
- package/dist/coordinator/hosted-heartbeat-execution-client.js.map +1 -0
- package/dist/coordinator/hosted-heartbeat-execution-service.d.ts +14 -0
- package/dist/coordinator/hosted-heartbeat-execution-service.d.ts.map +1 -0
- package/dist/coordinator/hosted-heartbeat-execution-service.js +100 -0
- package/dist/coordinator/hosted-heartbeat-execution-service.js.map +1 -0
- package/dist/coordinator/index.d.ts +5 -5
- package/dist/coordinator/index.d.ts.map +1 -1
- package/dist/coordinator/index.js +3 -3
- package/dist/coordinator/index.js.map +1 -1
- package/dist/coordinator/node/execution-http-service.d.ts +11 -0
- package/dist/coordinator/node/execution-http-service.d.ts.map +1 -0
- package/dist/coordinator/node/{delegation-http-service.js → execution-http-service.js} +52 -39
- package/dist/coordinator/node/execution-http-service.js.map +1 -0
- package/dist/coordinator/node/index.d.ts +2 -2
- package/dist/coordinator/node/index.d.ts.map +1 -1
- package/dist/coordinator/node/index.js +1 -1
- package/dist/coordinator/node/index.js.map +1 -1
- package/dist/coordinator/node/types.d.ts +10 -9
- package/dist/coordinator/node/types.d.ts.map +1 -1
- package/dist/coordinator/node/types.js.map +1 -1
- package/dist/coordinator/types.d.ts +51 -13
- package/dist/coordinator/types.d.ts.map +1 -1
- package/dist/coordinator/types.js.map +1 -1
- package/dist/heartbeat/hosted-heartbeat-task-service.js +2 -2
- package/dist/heartbeat/hosted-heartbeat-task-service.js.map +1 -1
- package/dist/host/jwt-execution-host-mcp-capability-verifier.d.ts.map +1 -1
- package/dist/host/jwt-execution-host-mcp-capability-verifier.js +4 -2
- package/dist/host/jwt-execution-host-mcp-capability-verifier.js.map +1 -1
- package/dist/host/jwt-execution-identity-verifier.d.ts.map +1 -1
- package/dist/host/jwt-execution-identity-verifier.js +4 -2
- package/dist/host/jwt-execution-identity-verifier.js.map +1 -1
- package/dist/host/runtime-session/service.d.ts +2 -2
- package/dist/host/runtime-session/service.d.ts.map +1 -1
- package/dist/host/runtime-session/service.js +1 -1
- package/dist/host/runtime-session/service.js.map +1 -1
- package/dist/host/runtime-session/types.d.ts +2 -2
- package/dist/host/runtime-session/types.d.ts.map +1 -1
- package/dist/host/runtime-session/types.js.map +1 -1
- package/dist/host/schemas.d.ts +6 -0
- package/dist/host/schemas.d.ts.map +1 -1
- package/dist/host/schemas.js +18 -1
- package/dist/host/schemas.js.map +1 -1
- package/dist/host/types.d.ts +2 -0
- package/dist/host/types.d.ts.map +1 -1
- package/dist/host/types.js.map +1 -1
- package/dist/http-sse/direct-http-execution-host.d.ts.map +1 -1
- package/dist/http-sse/direct-http-execution-host.js +8 -7
- package/dist/http-sse/direct-http-execution-host.js.map +1 -1
- package/dist/http-sse/types.d.ts +5 -3
- package/dist/http-sse/types.d.ts.map +1 -1
- package/dist/http-sse/types.js.map +1 -1
- package/dist/node/authority-key.d.ts.map +1 -1
- package/dist/node/authority-key.js +24 -49
- package/dist/node/authority-key.js.map +1 -1
- package/dist/node/direct-credentials.d.ts +3 -7
- package/dist/node/direct-credentials.d.ts.map +1 -1
- package/dist/node/direct-credentials.js +3 -15
- package/dist/node/direct-credentials.js.map +1 -1
- package/dist/node/environment-value.d.ts +17 -0
- package/dist/node/environment-value.d.ts.map +1 -0
- package/dist/node/environment-value.js +78 -0
- package/dist/node/environment-value.js.map +1 -0
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +2 -0
- package/dist/node/index.js.map +1 -1
- package/dist/node/local-credential-bundle.d.ts +33 -0
- package/dist/node/local-credential-bundle.d.ts.map +1 -0
- package/dist/node/local-credential-bundle.js +196 -0
- package/dist/node/local-credential-bundle.js.map +1 -0
- package/dist/node/owner-only-file.d.ts +17 -0
- package/dist/node/owner-only-file.d.ts.map +1 -0
- package/dist/node/owner-only-file.js +59 -0
- package/dist/node/owner-only-file.js.map +1 -0
- package/dist/testing/request.d.ts.map +1 -1
- package/dist/testing/request.js +14 -4
- package/dist/testing/request.js.map +1 -1
- package/package.json +5 -1
- package/spec/v1/openapi.json +62 -4
- package/spec/v1/schema-bundle.json +58 -0
- package/dist/coordinator/hosted-heartbeat-delegation-client.d.ts +0 -21
- package/dist/coordinator/hosted-heartbeat-delegation-client.d.ts.map +0 -1
- package/dist/coordinator/hosted-heartbeat-delegation-client.js +0 -117
- package/dist/coordinator/hosted-heartbeat-delegation-client.js.map +0 -1
- package/dist/coordinator/hosted-heartbeat-delegation-service.d.ts +0 -15
- package/dist/coordinator/hosted-heartbeat-delegation-service.d.ts.map +0 -1
- package/dist/coordinator/hosted-heartbeat-delegation-service.js +0 -81
- package/dist/coordinator/hosted-heartbeat-delegation-service.js.map +0 -1
- package/dist/coordinator/node/delegation-http-service.d.ts +0 -11
- package/dist/coordinator/node/delegation-http-service.d.ts.map +0 -1
- package/dist/coordinator/node/delegation-http-service.js.map +0 -1
package/dist/testing/request.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createHash, timingSafeEqual, } from 'node:crypto';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { AGENTCORE_RUNTIME_SESSION_HEADER, EXECUTION_ASSERTION_HEADER, EXECUTION_HOST_LOCAL_TOKEN_HEADER, ExecutionHostConversationTurnRequestSchema, MCP_CAPABILITY_HEADER,
|
|
3
|
+
import { AGENTCORE_RUNTIME_SESSION_HEADER, EXECUTION_ASSERTION_HEADER, EXECUTION_HOST_LOCAL_TOKEN_HEADER, ExecutionHostConversationTurnRequestSchema, ExecutionHostModelCredentialSchema, MCP_CAPABILITY_HEADER, MODEL_CREDENTIAL_HEADER, RuntimeSessionIdSchema, } from '../contracts/index.js';
|
|
4
4
|
const MAX_REQUEST_BODY_BYTES = 262_144;
|
|
5
5
|
const REDACTED_HEADER_VALUE = '[REDACTED]';
|
|
6
|
-
const SecretSchema = z.string().min(8).max(4_096);
|
|
7
6
|
const AssertionSchema = z.string().min(32).max(4_096);
|
|
8
7
|
const ErrorCodeSchema = z.string().min(1).max(128).regex(/^[a-z0-9_]+$/);
|
|
9
8
|
export async function parseLocalExecutionHostRequest(request, expectedLocalToken, signal) {
|
|
@@ -12,7 +11,7 @@ export async function parseLocalExecutionHostRequest(request, expectedLocalToken
|
|
|
12
11
|
assertLocalToken(headers.localToken, expectedLocalToken);
|
|
13
12
|
const runtimeSessionId = parseRequestValue(RuntimeSessionIdSchema, headers.runtimeSessionId, 400, 'invalid_runtime_session');
|
|
14
13
|
parseRequestValue(AssertionSchema, headers.executionAssertion, 401, 'invalid_execution_identity');
|
|
15
|
-
parseRequestValue(
|
|
14
|
+
parseRequestValue(ExecutionHostModelCredentialSchema, parseSensitiveJson(headers.modelCredential), 401, 'invalid_model_credential');
|
|
16
15
|
const mcpCapability = headers.mcpCapability === undefined
|
|
17
16
|
? undefined
|
|
18
17
|
: parseRequestValue(AssertionSchema, headers.mcpCapability, 401, 'invalid_mcp_capability');
|
|
@@ -57,13 +56,24 @@ function takeInvocationHeaders(request) {
|
|
|
57
56
|
runtimeSessionId: extractSensitiveHeader(request, AGENTCORE_RUNTIME_SESSION_HEADER),
|
|
58
57
|
executionAssertion: extractSensitiveHeader(request, EXECUTION_ASSERTION_HEADER),
|
|
59
58
|
mcpCapability: extractSensitiveHeader(request, MCP_CAPABILITY_HEADER),
|
|
60
|
-
|
|
59
|
+
modelCredential: extractSensitiveHeader(request, MODEL_CREDENTIAL_HEADER),
|
|
61
60
|
};
|
|
62
61
|
if (Object.values(extracted).some((header) => header.duplicate)) {
|
|
63
62
|
throw new LocalExecutionHostRequestError(400, 'duplicate_sensitive_header');
|
|
64
63
|
}
|
|
65
64
|
return Object.fromEntries(Object.entries(extracted).map(([name, header]) => [name, header.value]));
|
|
66
65
|
}
|
|
66
|
+
function parseSensitiveJson(value) {
|
|
67
|
+
if (!value) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
return JSON.parse(value);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
67
77
|
function extractSensitiveHeader(request, name) {
|
|
68
78
|
const occurrences = request.rawHeaders.reduce((count, headerName, index) => count
|
|
69
79
|
+ (index % 2 === 0 && headerName.toLowerCase() === name ? 1 : 0), 0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/testing/request.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createHash,\n timingSafeEqual,\n} from 'node:crypto';\nimport type {\n IncomingMessage,\n ServerResponse,\n} from 'node:http';\nimport { z } from 'zod';\nimport {\n AGENTCORE_RUNTIME_SESSION_HEADER,\n EXECUTION_ASSERTION_HEADER,\n EXECUTION_HOST_LOCAL_TOKEN_HEADER,\n ExecutionHostConversationTurnRequestSchema,\n MCP_CAPABILITY_HEADER,\n MODEL_API_KEY_HEADER,\n RuntimeSessionIdSchema,\n type ExecutionHostConversationTurnRequest,\n} from '../contracts/index.js';\n\nconst MAX_REQUEST_BODY_BYTES = 262_144;\nconst REDACTED_HEADER_VALUE = '[REDACTED]';\nconst SecretSchema = z.string().min(8).max(4_096);\nconst AssertionSchema = z.string().min(32).max(4_096);\nconst ErrorCodeSchema = z.string().min(1).max(128).regex(/^[a-z0-9_]+$/);\n\nexport type ParsedLocalExecutionHostRequest = {\n request: ExecutionHostConversationTurnRequest;\n runtimeSessionId: string;\n mcpCapability?: string;\n};\n\nexport async function parseLocalExecutionHostRequest(\n request: IncomingMessage,\n expectedLocalToken: string,\n signal: AbortSignal,\n): Promise<ParsedLocalExecutionHostRequest> {\n const headers = takeInvocationHeaders(request);\n assertInvocationRoute(request);\n assertLocalToken(headers.localToken, expectedLocalToken);\n const runtimeSessionId = parseRequestValue(\n RuntimeSessionIdSchema,\n headers.runtimeSessionId,\n 400,\n 'invalid_runtime_session',\n );\n parseRequestValue(\n AssertionSchema,\n headers.executionAssertion,\n 401,\n 'invalid_execution_identity',\n );\n parseRequestValue(\n SecretSchema,\n headers.modelApiKey,\n 401,\n 'invalid_model_credential',\n );\n const mcpCapability = headers.mcpCapability === undefined\n ? undefined\n : parseRequestValue(\n AssertionSchema,\n headers.mcpCapability,\n 401,\n 'invalid_mcp_capability',\n );\n const parsedRequest = parseRequestValue(\n ExecutionHostConversationTurnRequestSchema,\n await readJsonBody(request, signal),\n 400,\n 'invalid_request',\n );\n return {\n request: parsedRequest,\n runtimeSessionId,\n mcpCapability,\n };\n}\n\nexport function writeLocalExecutionHostRejection(\n response: ServerResponse,\n error: unknown,\n): void {\n const rejection = error instanceof LocalExecutionHostRequestError\n ? { status: error.status, code: error.code }\n : { status: 500, code: 'fixture_internal_error' };\n const safeCode = ErrorCodeSchema.parse(rejection.code);\n const body = JSON.stringify({\n error: {\n code: safeCode,\n message: 'Local Execution Host fixture rejected the request.',\n },\n });\n response.writeHead(rejection.status, {\n 'Cache-Control': 'no-store',\n 'Content-Length': Buffer.byteLength(body),\n 'Content-Type': 'application/json; charset=utf-8',\n 'X-Content-Type-Options': 'nosniff',\n });\n response.end(body);\n}\n\ntype InvocationHeaders = {\n localToken: string | undefined;\n runtimeSessionId: string | undefined;\n executionAssertion: string | undefined;\n mcpCapability: string | undefined;\n modelApiKey: string | undefined;\n};\n\nclass LocalExecutionHostRequestError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n ) {\n super(code);\n }\n}\n\nfunction takeInvocationHeaders(request: IncomingMessage): InvocationHeaders {\n const extracted = {\n localToken: extractSensitiveHeader(\n request,\n EXECUTION_HOST_LOCAL_TOKEN_HEADER,\n ),\n runtimeSessionId: extractSensitiveHeader(\n request,\n AGENTCORE_RUNTIME_SESSION_HEADER,\n ),\n executionAssertion: extractSensitiveHeader(\n request,\n EXECUTION_ASSERTION_HEADER,\n ),\n mcpCapability: extractSensitiveHeader(request, MCP_CAPABILITY_HEADER),\n modelApiKey: extractSensitiveHeader(request, MODEL_API_KEY_HEADER),\n };\n if (Object.values(extracted).some((header) => header.duplicate)) {\n throw new LocalExecutionHostRequestError(\n 400,\n 'duplicate_sensitive_header',\n );\n }\n return Object.fromEntries(\n Object.entries(extracted).map(([name, header]) => [name, header.value]),\n ) as InvocationHeaders;\n}\n\nfunction extractSensitiveHeader(\n request: IncomingMessage,\n name: string,\n): { value: string | undefined; duplicate: boolean } {\n const occurrences = request.rawHeaders.reduce(\n (count, headerName, index) => count\n + (index % 2 === 0 && headerName.toLowerCase() === name ? 1 : 0),\n 0,\n );\n const value = request.headers[name];\n request.headers[name] = value === undefined\n ? undefined\n : REDACTED_HEADER_VALUE;\n for (let index = 0; index < request.rawHeaders.length; index += 2) {\n if (request.rawHeaders[index]?.toLowerCase() === name) {\n request.rawHeaders[index + 1] = REDACTED_HEADER_VALUE;\n }\n }\n return {\n value: Array.isArray(value) ? undefined : value,\n duplicate: occurrences > 1 || Array.isArray(value),\n };\n}\n\nfunction assertInvocationRoute(request: IncomingMessage): void {\n if (request.method !== 'POST') {\n throw new LocalExecutionHostRequestError(405, 'method_not_allowed');\n }\n if (request.url !== '/invocations') {\n throw new LocalExecutionHostRequestError(404, 'not_found');\n }\n const contentType = request.headers['content-type']\n ?.split(';', 1)[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== 'application/json') {\n throw new LocalExecutionHostRequestError(\n 415,\n 'unsupported_media_type',\n );\n }\n}\n\nfunction assertLocalToken(actual: string | undefined, expected: string): void {\n if (!actual || !constantTimeEqual(actual, expected)) {\n throw new LocalExecutionHostRequestError(401, 'invalid_local_token');\n }\n}\n\nfunction constantTimeEqual(left: string, right: string): boolean {\n const leftHash = createHash('sha256').update(left).digest();\n const rightHash = createHash('sha256').update(right).digest();\n return timingSafeEqual(leftHash, rightHash);\n}\n\nasync function readJsonBody(\n request: IncomingMessage,\n signal: AbortSignal,\n): Promise<unknown> {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n for await (const rawChunk of request) {\n signal.throwIfAborted();\n const chunk = Buffer.isBuffer(rawChunk)\n ? rawChunk\n : Buffer.from(rawChunk as Uint8Array);\n totalBytes += chunk.byteLength;\n if (totalBytes > MAX_REQUEST_BODY_BYTES) {\n throw new LocalExecutionHostRequestError(413, 'request_too_large');\n }\n chunks.push(chunk);\n }\n if (totalBytes === 0) {\n throw new LocalExecutionHostRequestError(400, 'invalid_request');\n }\n try {\n return JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8'));\n } catch {\n throw new LocalExecutionHostRequestError(400, 'invalid_request');\n }\n}\n\nfunction parseRequestValue<T>(\n schema: z.ZodType<T>,\n value: unknown,\n status: number,\n code: string,\n): T {\n const parsed = schema.safeParse(value);\n if (!parsed.success) {\n throw new LocalExecutionHostRequestError(status, code);\n }\n return parsed.data;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/testing/request.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createHash,\n timingSafeEqual,\n} from 'node:crypto';\nimport type {\n IncomingMessage,\n ServerResponse,\n} from 'node:http';\nimport { z } from 'zod';\nimport {\n AGENTCORE_RUNTIME_SESSION_HEADER,\n EXECUTION_ASSERTION_HEADER,\n EXECUTION_HOST_LOCAL_TOKEN_HEADER,\n ExecutionHostConversationTurnRequestSchema,\n ExecutionHostModelCredentialSchema,\n MCP_CAPABILITY_HEADER,\n MODEL_CREDENTIAL_HEADER,\n RuntimeSessionIdSchema,\n type ExecutionHostConversationTurnRequest,\n} from '../contracts/index.js';\n\nconst MAX_REQUEST_BODY_BYTES = 262_144;\nconst REDACTED_HEADER_VALUE = '[REDACTED]';\nconst AssertionSchema = z.string().min(32).max(4_096);\nconst ErrorCodeSchema = z.string().min(1).max(128).regex(/^[a-z0-9_]+$/);\n\nexport type ParsedLocalExecutionHostRequest = {\n request: ExecutionHostConversationTurnRequest;\n runtimeSessionId: string;\n mcpCapability?: string;\n};\n\nexport async function parseLocalExecutionHostRequest(\n request: IncomingMessage,\n expectedLocalToken: string,\n signal: AbortSignal,\n): Promise<ParsedLocalExecutionHostRequest> {\n const headers = takeInvocationHeaders(request);\n assertInvocationRoute(request);\n assertLocalToken(headers.localToken, expectedLocalToken);\n const runtimeSessionId = parseRequestValue(\n RuntimeSessionIdSchema,\n headers.runtimeSessionId,\n 400,\n 'invalid_runtime_session',\n );\n parseRequestValue(\n AssertionSchema,\n headers.executionAssertion,\n 401,\n 'invalid_execution_identity',\n );\n parseRequestValue(\n ExecutionHostModelCredentialSchema,\n parseSensitiveJson(headers.modelCredential),\n 401,\n 'invalid_model_credential',\n );\n const mcpCapability = headers.mcpCapability === undefined\n ? undefined\n : parseRequestValue(\n AssertionSchema,\n headers.mcpCapability,\n 401,\n 'invalid_mcp_capability',\n );\n const parsedRequest = parseRequestValue(\n ExecutionHostConversationTurnRequestSchema,\n await readJsonBody(request, signal),\n 400,\n 'invalid_request',\n );\n return {\n request: parsedRequest,\n runtimeSessionId,\n mcpCapability,\n };\n}\n\nexport function writeLocalExecutionHostRejection(\n response: ServerResponse,\n error: unknown,\n): void {\n const rejection = error instanceof LocalExecutionHostRequestError\n ? { status: error.status, code: error.code }\n : { status: 500, code: 'fixture_internal_error' };\n const safeCode = ErrorCodeSchema.parse(rejection.code);\n const body = JSON.stringify({\n error: {\n code: safeCode,\n message: 'Local Execution Host fixture rejected the request.',\n },\n });\n response.writeHead(rejection.status, {\n 'Cache-Control': 'no-store',\n 'Content-Length': Buffer.byteLength(body),\n 'Content-Type': 'application/json; charset=utf-8',\n 'X-Content-Type-Options': 'nosniff',\n });\n response.end(body);\n}\n\ntype InvocationHeaders = {\n localToken: string | undefined;\n runtimeSessionId: string | undefined;\n executionAssertion: string | undefined;\n mcpCapability: string | undefined;\n modelCredential: string | undefined;\n};\n\nclass LocalExecutionHostRequestError extends Error {\n constructor(\n readonly status: number,\n readonly code: string,\n ) {\n super(code);\n }\n}\n\nfunction takeInvocationHeaders(request: IncomingMessage): InvocationHeaders {\n const extracted = {\n localToken: extractSensitiveHeader(\n request,\n EXECUTION_HOST_LOCAL_TOKEN_HEADER,\n ),\n runtimeSessionId: extractSensitiveHeader(\n request,\n AGENTCORE_RUNTIME_SESSION_HEADER,\n ),\n executionAssertion: extractSensitiveHeader(\n request,\n EXECUTION_ASSERTION_HEADER,\n ),\n mcpCapability: extractSensitiveHeader(request, MCP_CAPABILITY_HEADER),\n modelCredential: extractSensitiveHeader(request, MODEL_CREDENTIAL_HEADER),\n };\n if (Object.values(extracted).some((header) => header.duplicate)) {\n throw new LocalExecutionHostRequestError(\n 400,\n 'duplicate_sensitive_header',\n );\n }\n return Object.fromEntries(\n Object.entries(extracted).map(([name, header]) => [name, header.value]),\n ) as InvocationHeaders;\n}\n\nfunction parseSensitiveJson(value: string | undefined): unknown {\n if (!value) {\n return undefined;\n }\n try {\n return JSON.parse(value) as unknown;\n } catch {\n return undefined;\n }\n}\n\nfunction extractSensitiveHeader(\n request: IncomingMessage,\n name: string,\n): { value: string | undefined; duplicate: boolean } {\n const occurrences = request.rawHeaders.reduce(\n (count, headerName, index) => count\n + (index % 2 === 0 && headerName.toLowerCase() === name ? 1 : 0),\n 0,\n );\n const value = request.headers[name];\n request.headers[name] = value === undefined\n ? undefined\n : REDACTED_HEADER_VALUE;\n for (let index = 0; index < request.rawHeaders.length; index += 2) {\n if (request.rawHeaders[index]?.toLowerCase() === name) {\n request.rawHeaders[index + 1] = REDACTED_HEADER_VALUE;\n }\n }\n return {\n value: Array.isArray(value) ? undefined : value,\n duplicate: occurrences > 1 || Array.isArray(value),\n };\n}\n\nfunction assertInvocationRoute(request: IncomingMessage): void {\n if (request.method !== 'POST') {\n throw new LocalExecutionHostRequestError(405, 'method_not_allowed');\n }\n if (request.url !== '/invocations') {\n throw new LocalExecutionHostRequestError(404, 'not_found');\n }\n const contentType = request.headers['content-type']\n ?.split(';', 1)[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== 'application/json') {\n throw new LocalExecutionHostRequestError(\n 415,\n 'unsupported_media_type',\n );\n }\n}\n\nfunction assertLocalToken(actual: string | undefined, expected: string): void {\n if (!actual || !constantTimeEqual(actual, expected)) {\n throw new LocalExecutionHostRequestError(401, 'invalid_local_token');\n }\n}\n\nfunction constantTimeEqual(left: string, right: string): boolean {\n const leftHash = createHash('sha256').update(left).digest();\n const rightHash = createHash('sha256').update(right).digest();\n return timingSafeEqual(leftHash, rightHash);\n}\n\nasync function readJsonBody(\n request: IncomingMessage,\n signal: AbortSignal,\n): Promise<unknown> {\n const chunks: Buffer[] = [];\n let totalBytes = 0;\n for await (const rawChunk of request) {\n signal.throwIfAborted();\n const chunk = Buffer.isBuffer(rawChunk)\n ? rawChunk\n : Buffer.from(rawChunk as Uint8Array);\n totalBytes += chunk.byteLength;\n if (totalBytes > MAX_REQUEST_BODY_BYTES) {\n throw new LocalExecutionHostRequestError(413, 'request_too_large');\n }\n chunks.push(chunk);\n }\n if (totalBytes === 0) {\n throw new LocalExecutionHostRequestError(400, 'invalid_request');\n }\n try {\n return JSON.parse(Buffer.concat(chunks, totalBytes).toString('utf8'));\n } catch {\n throw new LocalExecutionHostRequestError(400, 'invalid_request');\n }\n}\n\nfunction parseRequestValue<T>(\n schema: z.ZodType<T>,\n value: unknown,\n status: number,\n code: string,\n): T {\n const parsed = schema.safeParse(value);\n if (!parsed.success) {\n throw new LocalExecutionHostRequestError(status, code);\n }\n return parsed.data;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heddleagent/execution-host-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Contracts, execution authority, lifecycle services, and adopter clients for compatible Heddle Execution Hosts",
|
|
5
5
|
"author": "Jay / Fienna Liang <roackb2@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
"main": "./dist/index.js",
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
|
+
"bin": {
|
|
24
|
+
"heddle-hostctl": "./dist/cli/hostctl.js"
|
|
25
|
+
},
|
|
23
26
|
"exports": {
|
|
24
27
|
".": {
|
|
25
28
|
"types": "./dist/index.d.ts",
|
|
@@ -115,6 +118,7 @@
|
|
|
115
118
|
"@aws-sdk/client-bedrock-agentcore": "^3.1107.0",
|
|
116
119
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
117
120
|
"@smithy/protocol-http": "^5.5.16",
|
|
121
|
+
"commander": "^14.0.3",
|
|
118
122
|
"dayjs": "^1.11.20",
|
|
119
123
|
"eventsource-parser": "^3.1.0",
|
|
120
124
|
"jose": "^6.2.8",
|
package/spec/v1/openapi.json
CHANGED
|
@@ -49,14 +49,14 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
|
-
"name": "x-heddle-execution-host-model-
|
|
52
|
+
"name": "x-heddle-execution-host-model-credential",
|
|
53
53
|
"in": "header",
|
|
54
54
|
"required": true,
|
|
55
|
-
"description": "
|
|
55
|
+
"description": "JSON-encoded invocation-scoped model credential. OAuth refresh material is forbidden. The header must be scrubbed before parsing or logging.",
|
|
56
56
|
"schema": {
|
|
57
57
|
"type": "string",
|
|
58
|
-
"minLength":
|
|
59
|
-
"maxLength":
|
|
58
|
+
"minLength": 16,
|
|
59
|
+
"maxLength": 8192
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
{
|
|
@@ -441,6 +441,64 @@
|
|
|
441
441
|
}
|
|
442
442
|
}
|
|
443
443
|
},
|
|
444
|
+
"ModelCredential": {
|
|
445
|
+
"oneOf": [
|
|
446
|
+
{
|
|
447
|
+
"type": "object",
|
|
448
|
+
"properties": {
|
|
449
|
+
"type": {
|
|
450
|
+
"type": "string",
|
|
451
|
+
"const": "api-key"
|
|
452
|
+
},
|
|
453
|
+
"apiKey": {
|
|
454
|
+
"type": "string",
|
|
455
|
+
"minLength": 8,
|
|
456
|
+
"maxLength": 4096
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
"required": [
|
|
460
|
+
"type",
|
|
461
|
+
"apiKey"
|
|
462
|
+
],
|
|
463
|
+
"additionalProperties": false
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"type": "object",
|
|
467
|
+
"properties": {
|
|
468
|
+
"type": {
|
|
469
|
+
"type": "string",
|
|
470
|
+
"const": "oauth-access-token"
|
|
471
|
+
},
|
|
472
|
+
"provider": {
|
|
473
|
+
"type": "string",
|
|
474
|
+
"const": "openai"
|
|
475
|
+
},
|
|
476
|
+
"accessToken": {
|
|
477
|
+
"type": "string",
|
|
478
|
+
"minLength": 8,
|
|
479
|
+
"maxLength": 4096
|
|
480
|
+
},
|
|
481
|
+
"expiresAt": {
|
|
482
|
+
"type": "integer",
|
|
483
|
+
"exclusiveMinimum": 0,
|
|
484
|
+
"maximum": 9007199254740991
|
|
485
|
+
},
|
|
486
|
+
"accountId": {
|
|
487
|
+
"type": "string",
|
|
488
|
+
"minLength": 1,
|
|
489
|
+
"maxLength": 512
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
"required": [
|
|
493
|
+
"type",
|
|
494
|
+
"provider",
|
|
495
|
+
"accessToken",
|
|
496
|
+
"expiresAt"
|
|
497
|
+
],
|
|
498
|
+
"additionalProperties": false
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
},
|
|
444
502
|
"RuntimePublicResult": {
|
|
445
503
|
"type": "object",
|
|
446
504
|
"properties": {
|
|
@@ -242,6 +242,64 @@
|
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
},
|
|
245
|
+
"ModelCredential": {
|
|
246
|
+
"oneOf": [
|
|
247
|
+
{
|
|
248
|
+
"type": "object",
|
|
249
|
+
"properties": {
|
|
250
|
+
"type": {
|
|
251
|
+
"type": "string",
|
|
252
|
+
"const": "api-key"
|
|
253
|
+
},
|
|
254
|
+
"apiKey": {
|
|
255
|
+
"type": "string",
|
|
256
|
+
"minLength": 8,
|
|
257
|
+
"maxLength": 4096
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"required": [
|
|
261
|
+
"type",
|
|
262
|
+
"apiKey"
|
|
263
|
+
],
|
|
264
|
+
"additionalProperties": false
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"type": "object",
|
|
268
|
+
"properties": {
|
|
269
|
+
"type": {
|
|
270
|
+
"type": "string",
|
|
271
|
+
"const": "oauth-access-token"
|
|
272
|
+
},
|
|
273
|
+
"provider": {
|
|
274
|
+
"type": "string",
|
|
275
|
+
"const": "openai"
|
|
276
|
+
},
|
|
277
|
+
"accessToken": {
|
|
278
|
+
"type": "string",
|
|
279
|
+
"minLength": 8,
|
|
280
|
+
"maxLength": 4096
|
|
281
|
+
},
|
|
282
|
+
"expiresAt": {
|
|
283
|
+
"type": "integer",
|
|
284
|
+
"exclusiveMinimum": 0,
|
|
285
|
+
"maximum": 9007199254740991
|
|
286
|
+
},
|
|
287
|
+
"accountId": {
|
|
288
|
+
"type": "string",
|
|
289
|
+
"minLength": 1,
|
|
290
|
+
"maxLength": 512
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
"required": [
|
|
294
|
+
"type",
|
|
295
|
+
"provider",
|
|
296
|
+
"accessToken",
|
|
297
|
+
"expiresAt"
|
|
298
|
+
],
|
|
299
|
+
"additionalProperties": false
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
},
|
|
245
303
|
"RuntimePublicResult": {
|
|
246
304
|
"type": "object",
|
|
247
305
|
"properties": {
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { type HostedHeartbeatAgentExecutionTransportInput } from '../heartbeat/index.js';
|
|
2
|
-
import { type HostedHeartbeatDelegation, type HostedHeartbeatDelegationRequest } from './contracts.js';
|
|
3
|
-
import type { HostedHeartbeatDelegatedExecutionTransportConfig, HostedHeartbeatDelegatedExecutionTransportPort, HostedHeartbeatDelegationClientConfig, HostedHeartbeatDelegationIssuer } from './types.js';
|
|
4
|
-
export declare class HostedHeartbeatDelegationRequestError extends Error {
|
|
5
|
-
readonly status: number;
|
|
6
|
-
readonly name = "HostedHeartbeatDelegationRequestError";
|
|
7
|
-
constructor(status: number);
|
|
8
|
-
}
|
|
9
|
-
/** Authenticated coordinator client for one product-issued authority bundle. */
|
|
10
|
-
export declare class HostedHeartbeatDelegationClient implements HostedHeartbeatDelegationIssuer {
|
|
11
|
-
#private;
|
|
12
|
-
constructor(config: HostedHeartbeatDelegationClientConfig);
|
|
13
|
-
issue(rawInput: HostedHeartbeatDelegationRequest, signal?: AbortSignal): Promise<HostedHeartbeatDelegation>;
|
|
14
|
-
}
|
|
15
|
-
/** Executes a claimed task with the exact product-issued authority bundle. */
|
|
16
|
-
export declare class HostedHeartbeatDelegatedExecutionTransport implements HostedHeartbeatDelegatedExecutionTransportPort {
|
|
17
|
-
#private;
|
|
18
|
-
constructor(config: HostedHeartbeatDelegatedExecutionTransportConfig);
|
|
19
|
-
execute(input: HostedHeartbeatAgentExecutionTransportInput): Promise<unknown>;
|
|
20
|
-
}
|
|
21
|
-
//# sourceMappingURL=hosted-heartbeat-delegation-client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-heartbeat-delegation-client.d.ts","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-client.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,2CAA2C,EACjD,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAIL,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACtC,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EACV,gDAAgD,EAChD,8CAA8C,EAC9C,qCAAqC,EACrC,+BAA+B,EAChC,MAAM,YAAY,CAAC;AAWpB,qBAAa,qCAAsC,SAAQ,KAAK;IAGlD,QAAQ,CAAC,MAAM,EAAE,MAAM;IAFnC,QAAQ,CAAC,IAAI,2CAA2C;gBAEnC,MAAM,EAAE,MAAM;CAGpC;AAED,gFAAgF;AAChF,qBAAa,+BACb,YAAW,+BAA+B;;gBAK5B,MAAM,EAAE,qCAAqC;IAcnD,KAAK,CACT,QAAQ,EAAE,gCAAgC,EAC1C,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,yBAAyB,CAAC;CA4BtC;AAED,8EAA8E;AAC9E,qBAAa,0CACb,YAAW,8CAA8C;;gBAG3C,MAAM,EAAE,gDAAgD;IAI9D,OAAO,CACX,KAAK,EAAE,2CAA2C,GACjD,OAAO,CAAC,OAAO,CAAC;CAwBpB"}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { HEARTBEAT_TASK_WORKFLOW, isSafeWebUrl, } from '../contracts/index.js';
|
|
3
|
-
import { HostedHeartbeatAgentExecutionTransport, HostedHeartbeatTaskService, } from '../heartbeat/index.js';
|
|
4
|
-
import { HOSTED_HEARTBEAT_DELEGATIONS_PATH, HostedHeartbeatDelegationRequestSchema, HostedHeartbeatDelegationSchema, } from './contracts.js';
|
|
5
|
-
import { HostedHeartbeatServiceTokenSchema } from './service-token.js';
|
|
6
|
-
const SafeBaseUrlSchema = z.custom((value) => value instanceof URL && isSafeWebUrl(value), 'baseUrl must be a safe HTTPS or loopback HTTP URL');
|
|
7
|
-
const AbsolutePathSchema = z.string().min(1).max(256).regex(/^\/(?:[^?#\s]*)$/, 'must be an absolute path without query, fragment, or whitespace');
|
|
8
|
-
export class HostedHeartbeatDelegationRequestError extends Error {
|
|
9
|
-
status;
|
|
10
|
-
name = 'HostedHeartbeatDelegationRequestError';
|
|
11
|
-
constructor(status) {
|
|
12
|
-
super(`Heartbeat delegation failed with status ${status}.`);
|
|
13
|
-
this.status = status;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/** Authenticated coordinator client for one product-issued authority bundle. */
|
|
17
|
-
export class HostedHeartbeatDelegationClient {
|
|
18
|
-
#endpoint;
|
|
19
|
-
#authorization;
|
|
20
|
-
#fetch;
|
|
21
|
-
constructor(config) {
|
|
22
|
-
const baseUrl = SafeBaseUrlSchema.parse(config.baseUrl);
|
|
23
|
-
this.#endpoint = new URL(AbsolutePathSchema.parse(config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH), baseUrl);
|
|
24
|
-
this.#authorization = `Bearer ${HostedHeartbeatServiceTokenSchema.parse(config.apiToken)}`;
|
|
25
|
-
this.#fetch = config.fetch ?? globalThis.fetch;
|
|
26
|
-
}
|
|
27
|
-
async issue(rawInput, signal) {
|
|
28
|
-
const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);
|
|
29
|
-
const response = await this.#fetch(this.#endpoint, {
|
|
30
|
-
method: 'POST',
|
|
31
|
-
headers: {
|
|
32
|
-
authorization: this.#authorization,
|
|
33
|
-
'content-type': 'application/json',
|
|
34
|
-
},
|
|
35
|
-
body: JSON.stringify(input),
|
|
36
|
-
redirect: 'error',
|
|
37
|
-
signal,
|
|
38
|
-
});
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
throw new HostedHeartbeatDelegationRequestError(response.status);
|
|
41
|
-
}
|
|
42
|
-
const delegation = HostedHeartbeatDelegationSchema.parse(await response.json());
|
|
43
|
-
if (delegation.taskId !== input.taskId
|
|
44
|
-
|| delegation.executionId !== input.executionId) {
|
|
45
|
-
throw new Error('Product backend returned a mismatched heartbeat delegation.');
|
|
46
|
-
}
|
|
47
|
-
return delegation;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
/** Executes a claimed task with the exact product-issued authority bundle. */
|
|
51
|
-
export class HostedHeartbeatDelegatedExecutionTransport {
|
|
52
|
-
#config;
|
|
53
|
-
constructor(config) {
|
|
54
|
-
this.#config = config;
|
|
55
|
-
}
|
|
56
|
-
async execute(input) {
|
|
57
|
-
const delegation = await this.#config.delegations.issue({
|
|
58
|
-
schemaVersion: 1,
|
|
59
|
-
taskId: input.request.taskId,
|
|
60
|
-
executionId: input.request.executionId,
|
|
61
|
-
}, input.signal);
|
|
62
|
-
const service = new HostedHeartbeatTaskService({
|
|
63
|
-
authority: new DelegatedExecutionAuthorityIssuer(delegation),
|
|
64
|
-
executionHost: this.#config.executionHost,
|
|
65
|
-
modelCredentials: this.#config.modelCredentials,
|
|
66
|
-
mcp: {
|
|
67
|
-
allowedTools: delegation.authority.metadata.mcp.allowedTools,
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
const transport = new HostedHeartbeatAgentExecutionTransport({
|
|
71
|
-
runner: service,
|
|
72
|
-
resolveInvocationContext: () => ({
|
|
73
|
-
scope: delegation.scope,
|
|
74
|
-
runtimeSessionId: delegation.runtimeSessionId,
|
|
75
|
-
deadlineAt: delegation.deadlineAt,
|
|
76
|
-
}),
|
|
77
|
-
});
|
|
78
|
-
return await transport.execute(input);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Ensures downstream execution cannot widen a delegated authority bundle. */
|
|
82
|
-
class DelegatedExecutionAuthorityIssuer {
|
|
83
|
-
delegation;
|
|
84
|
-
constructor(delegation) {
|
|
85
|
-
this.delegation = delegation;
|
|
86
|
-
}
|
|
87
|
-
async issue(input) {
|
|
88
|
-
const metadata = this.delegation.authority.metadata;
|
|
89
|
-
if (input.workflow !== HEARTBEAT_TASK_WORKFLOW
|
|
90
|
-
|| input.workflow !== metadata.workflow
|
|
91
|
-
|| input.invocationId !== metadata.invocationId
|
|
92
|
-
|| input.runtimeSessionId !== metadata.runtimeSessionId
|
|
93
|
-
|| !sameScope(input.scope, this.delegation.scope)
|
|
94
|
-
|| !sameScope(input.scope, metadata.scope)
|
|
95
|
-
|| !sameTools(input.mcp?.allowedTools, metadata.mcp.allowedTools)) {
|
|
96
|
-
throw new Error('Heartbeat delegation does not match Heddle execution.');
|
|
97
|
-
}
|
|
98
|
-
const authority = this.delegation.authority;
|
|
99
|
-
return Object.freeze({
|
|
100
|
-
metadata: metadata,
|
|
101
|
-
executionAssertion: () => authority.executionAssertion,
|
|
102
|
-
mcpCapability: () => authority.mcpCapability,
|
|
103
|
-
toJSON: () => metadata,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
function sameScope(left, right) {
|
|
108
|
-
return left.tenantId === right.tenantId
|
|
109
|
-
&& left.subjectId === right.subjectId
|
|
110
|
-
&& left.productSessionId === right.productSessionId;
|
|
111
|
-
}
|
|
112
|
-
function sameTools(left, right) {
|
|
113
|
-
return Boolean(left
|
|
114
|
-
&& left.length === right.length
|
|
115
|
-
&& left.every((tool, index) => tool === right[index]));
|
|
116
|
-
}
|
|
117
|
-
//# sourceMappingURL=hosted-heartbeat-delegation-client.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-heartbeat-delegation-client.js","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type {\n ExecutionAuthorityIssueInput,\n ExecutionAuthorityIssuer,\n IssuedExecutionAuthority,\n IssuedExecutionAuthorityMetadata,\n} from '../authority/index.js';\nimport {\n HEARTBEAT_TASK_WORKFLOW,\n isSafeWebUrl,\n} from '../contracts/index.js';\nimport {\n HostedHeartbeatAgentExecutionTransport,\n HostedHeartbeatTaskService,\n type HostedHeartbeatAgentExecutionTransportInput,\n} from '../heartbeat/index.js';\nimport {\n HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n HostedHeartbeatDelegationRequestSchema,\n HostedHeartbeatDelegationSchema,\n type HostedHeartbeatDelegation,\n type HostedHeartbeatDelegationRequest,\n} from './contracts.js';\nimport { HostedHeartbeatServiceTokenSchema } from './service-token.js';\nimport type {\n HostedHeartbeatDelegatedExecutionTransportConfig,\n HostedHeartbeatDelegatedExecutionTransportPort,\n HostedHeartbeatDelegationClientConfig,\n HostedHeartbeatDelegationIssuer,\n} from './types.js';\n\nconst SafeBaseUrlSchema = z.custom<URL>(\n (value) => value instanceof URL && isSafeWebUrl(value),\n 'baseUrl must be a safe HTTPS or loopback HTTP URL',\n);\nconst AbsolutePathSchema = z.string().min(1).max(256).regex(\n /^\\/(?:[^?#\\s]*)$/,\n 'must be an absolute path without query, fragment, or whitespace',\n);\n\nexport class HostedHeartbeatDelegationRequestError extends Error {\n readonly name = 'HostedHeartbeatDelegationRequestError';\n\n constructor(readonly status: number) {\n super(`Heartbeat delegation failed with status ${status}.`);\n }\n}\n\n/** Authenticated coordinator client for one product-issued authority bundle. */\nexport class HostedHeartbeatDelegationClient\nimplements HostedHeartbeatDelegationIssuer {\n readonly #endpoint: URL;\n readonly #authorization: string;\n readonly #fetch: typeof globalThis.fetch;\n\n constructor(config: HostedHeartbeatDelegationClientConfig) {\n const baseUrl = SafeBaseUrlSchema.parse(config.baseUrl);\n this.#endpoint = new URL(\n AbsolutePathSchema.parse(\n config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n ),\n baseUrl,\n );\n this.#authorization = `Bearer ${\n HostedHeartbeatServiceTokenSchema.parse(config.apiToken)\n }`;\n this.#fetch = config.fetch ?? globalThis.fetch;\n }\n\n async issue(\n rawInput: HostedHeartbeatDelegationRequest,\n signal?: AbortSignal,\n ): Promise<HostedHeartbeatDelegation> {\n const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);\n const response = await this.#fetch(this.#endpoint, {\n method: 'POST',\n headers: {\n authorization: this.#authorization,\n 'content-type': 'application/json',\n },\n body: JSON.stringify(input),\n redirect: 'error',\n signal,\n });\n if (!response.ok) {\n throw new HostedHeartbeatDelegationRequestError(response.status);\n }\n const delegation = HostedHeartbeatDelegationSchema.parse(\n await response.json(),\n );\n if (\n delegation.taskId !== input.taskId\n || delegation.executionId !== input.executionId\n ) {\n throw new Error(\n 'Product backend returned a mismatched heartbeat delegation.',\n );\n }\n return delegation;\n }\n}\n\n/** Executes a claimed task with the exact product-issued authority bundle. */\nexport class HostedHeartbeatDelegatedExecutionTransport\nimplements HostedHeartbeatDelegatedExecutionTransportPort {\n readonly #config: HostedHeartbeatDelegatedExecutionTransportConfig;\n\n constructor(config: HostedHeartbeatDelegatedExecutionTransportConfig) {\n this.#config = config;\n }\n\n async execute(\n input: HostedHeartbeatAgentExecutionTransportInput,\n ): Promise<unknown> {\n const delegation = await this.#config.delegations.issue({\n schemaVersion: 1,\n taskId: input.request.taskId,\n executionId: input.request.executionId,\n }, input.signal);\n const service = new HostedHeartbeatTaskService({\n authority: new DelegatedExecutionAuthorityIssuer(delegation),\n executionHost: this.#config.executionHost,\n modelCredentials: this.#config.modelCredentials,\n mcp: {\n allowedTools: delegation.authority.metadata.mcp.allowedTools,\n },\n });\n const transport = new HostedHeartbeatAgentExecutionTransport({\n runner: service,\n resolveInvocationContext: () => ({\n scope: delegation.scope,\n runtimeSessionId: delegation.runtimeSessionId,\n deadlineAt: delegation.deadlineAt,\n }),\n });\n return await transport.execute(input);\n }\n}\n\n/** Ensures downstream execution cannot widen a delegated authority bundle. */\nclass DelegatedExecutionAuthorityIssuer implements ExecutionAuthorityIssuer {\n constructor(private readonly delegation: HostedHeartbeatDelegation) {}\n\n async issue(\n input: ExecutionAuthorityIssueInput,\n ): Promise<IssuedExecutionAuthority> {\n const metadata = this.delegation.authority.metadata;\n if (\n input.workflow !== HEARTBEAT_TASK_WORKFLOW\n || input.workflow !== metadata.workflow\n || input.invocationId !== metadata.invocationId\n || input.runtimeSessionId !== metadata.runtimeSessionId\n || !sameScope(input.scope, this.delegation.scope)\n || !sameScope(input.scope, metadata.scope)\n || !sameTools(input.mcp?.allowedTools, metadata.mcp.allowedTools)\n ) {\n throw new Error('Heartbeat delegation does not match Heddle execution.');\n }\n const authority = this.delegation.authority;\n return Object.freeze({\n metadata: metadata as IssuedExecutionAuthorityMetadata,\n executionAssertion: () => authority.executionAssertion,\n mcpCapability: () => authority.mcpCapability,\n toJSON: () => metadata as IssuedExecutionAuthorityMetadata,\n });\n }\n}\n\nfunction sameScope(\n left: ExecutionAuthorityIssueInput['scope'],\n right: HostedHeartbeatDelegation['scope']\n | HostedHeartbeatDelegation['authority']['metadata']['scope'],\n): boolean {\n return left.tenantId === right.tenantId\n && left.subjectId === right.subjectId\n && left.productSessionId === right.productSessionId;\n}\n\nfunction sameTools(\n left: readonly string[] | undefined,\n right: readonly string[],\n): boolean {\n return Boolean(\n left\n && left.length === right.length\n && left.every((tool, index) => tool === right[index]),\n );\n}\n"]}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type HostedHeartbeatDelegation, type HostedHeartbeatDelegationRequest } from './contracts.js';
|
|
2
|
-
import type { HostedHeartbeatDelegationIssuer, HostedHeartbeatDelegationServiceConfig } from './types.js';
|
|
3
|
-
export declare class HostedHeartbeatDelegationRejectedError extends Error {
|
|
4
|
-
readonly name = "HostedHeartbeatDelegationRejectedError";
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Converts one product authorization decision into a fully bound Heddle
|
|
8
|
-
* authority bundle. Product code never constructs the delegation wire shape.
|
|
9
|
-
*/
|
|
10
|
-
export declare class HostedHeartbeatDelegationService implements HostedHeartbeatDelegationIssuer {
|
|
11
|
-
#private;
|
|
12
|
-
constructor(config: HostedHeartbeatDelegationServiceConfig);
|
|
13
|
-
issue(rawInput: HostedHeartbeatDelegationRequest, rawSignal?: AbortSignal): Promise<HostedHeartbeatDelegation>;
|
|
14
|
-
}
|
|
15
|
-
//# sourceMappingURL=hosted-heartbeat-delegation-service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-heartbeat-delegation-service.d.ts","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,yBAAyB,EAC9B,KAAK,gCAAgC,EACtC,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EACV,+BAA+B,EAC/B,sCAAsC,EACvC,MAAM,YAAY,CAAC;AAOpB,qBAAa,sCAAuC,SAAQ,KAAK;IAC/D,QAAQ,CAAC,IAAI,4CAA4C;CAC1D;AAED;;;GAGG;AACH,qBAAa,gCACb,YAAW,+BAA+B;;gBAO5B,MAAM,EAAE,sCAAsC;IAYpD,KAAK,CACT,QAAQ,EAAE,gCAAgC,EAC1C,SAAS,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,yBAAyB,CAAC;CAqDtC"}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import dayjs from 'dayjs';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { HEARTBEAT_TASK_WORKFLOW, OpaqueIdSchema } from '../contracts/index.js';
|
|
4
|
-
import { HostedHeartbeatDelegationAuthorizationSchema, HostedHeartbeatDelegationRequestSchema, HostedHeartbeatDelegationSchema, } from './contracts.js';
|
|
5
|
-
import { createHostedRuntimeSessionId } from './runtime-session.js';
|
|
6
|
-
const DelegationServiceConfigSchema = z.object({
|
|
7
|
-
runtimeSessionNamespace: OpaqueIdSchema,
|
|
8
|
-
maxExecutionMs: z.number().int().min(1_000).max(Number.MAX_SAFE_INTEGER),
|
|
9
|
-
}).strict();
|
|
10
|
-
export class HostedHeartbeatDelegationRejectedError extends Error {
|
|
11
|
-
name = 'HostedHeartbeatDelegationRejectedError';
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Converts one product authorization decision into a fully bound Heddle
|
|
15
|
-
* authority bundle. Product code never constructs the delegation wire shape.
|
|
16
|
-
*/
|
|
17
|
-
export class HostedHeartbeatDelegationService {
|
|
18
|
-
#authority;
|
|
19
|
-
#authorizer;
|
|
20
|
-
#runtimeSessionNamespace;
|
|
21
|
-
#maxExecutionMs;
|
|
22
|
-
#now;
|
|
23
|
-
constructor(config) {
|
|
24
|
-
const parsed = DelegationServiceConfigSchema.parse({
|
|
25
|
-
runtimeSessionNamespace: config.runtimeSessionNamespace,
|
|
26
|
-
maxExecutionMs: config.maxExecutionMs,
|
|
27
|
-
});
|
|
28
|
-
this.#authority = config.authority;
|
|
29
|
-
this.#authorizer = config.authorizer;
|
|
30
|
-
this.#runtimeSessionNamespace = parsed.runtimeSessionNamespace;
|
|
31
|
-
this.#maxExecutionMs = parsed.maxExecutionMs;
|
|
32
|
-
this.#now = config.now ?? (() => new Date());
|
|
33
|
-
}
|
|
34
|
-
async issue(rawInput, rawSignal) {
|
|
35
|
-
const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);
|
|
36
|
-
const signal = rawSignal ?? new AbortController().signal;
|
|
37
|
-
signal.throwIfAborted();
|
|
38
|
-
const rawAuthorization = await this.#authorizer.authorize({
|
|
39
|
-
taskId: input.taskId,
|
|
40
|
-
executionId: input.executionId,
|
|
41
|
-
signal,
|
|
42
|
-
});
|
|
43
|
-
if (!rawAuthorization) {
|
|
44
|
-
throw new HostedHeartbeatDelegationRejectedError('The product rejected this heartbeat execution.');
|
|
45
|
-
}
|
|
46
|
-
const authorization = HostedHeartbeatDelegationAuthorizationSchema.parse(rawAuthorization);
|
|
47
|
-
const runtimeSessionId = createHostedRuntimeSessionId({
|
|
48
|
-
namespace: this.#runtimeSessionNamespace,
|
|
49
|
-
scope: authorization.scope,
|
|
50
|
-
});
|
|
51
|
-
const deadlineAt = dayjs(this.#now())
|
|
52
|
-
.add(this.#maxExecutionMs, 'millisecond')
|
|
53
|
-
.toISOString();
|
|
54
|
-
const issued = await this.#authority.issue({
|
|
55
|
-
scope: authorization.scope,
|
|
56
|
-
runtimeSessionId,
|
|
57
|
-
invocationId: input.executionId,
|
|
58
|
-
workflow: HEARTBEAT_TASK_WORKFLOW,
|
|
59
|
-
mcp: { allowedTools: authorization.allowedTools },
|
|
60
|
-
});
|
|
61
|
-
signal.throwIfAborted();
|
|
62
|
-
const mcpCapability = issued.mcpCapability();
|
|
63
|
-
if (!mcpCapability || !issued.metadata.mcp) {
|
|
64
|
-
throw new Error('Hosted heartbeat delegation requires an MCP-capable execution authority.');
|
|
65
|
-
}
|
|
66
|
-
return HostedHeartbeatDelegationSchema.parse({
|
|
67
|
-
schemaVersion: 1,
|
|
68
|
-
taskId: input.taskId,
|
|
69
|
-
executionId: input.executionId,
|
|
70
|
-
scope: authorization.scope,
|
|
71
|
-
runtimeSessionId,
|
|
72
|
-
deadlineAt,
|
|
73
|
-
authority: {
|
|
74
|
-
metadata: issued.metadata,
|
|
75
|
-
executionAssertion: issued.executionAssertion(),
|
|
76
|
-
mcpCapability,
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
//# sourceMappingURL=hosted-heartbeat-delegation-service.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hosted-heartbeat-delegation-service.js","sourceRoot":"","sources":["../../src/coordinator/hosted-heartbeat-delegation-service.ts"],"names":[],"mappings":"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","sourcesContent":["import dayjs from 'dayjs';\nimport { z } from 'zod';\nimport { HEARTBEAT_TASK_WORKFLOW, OpaqueIdSchema } from '../contracts/index.js';\nimport {\n HostedHeartbeatDelegationAuthorizationSchema,\n HostedHeartbeatDelegationRequestSchema,\n HostedHeartbeatDelegationSchema,\n type HostedHeartbeatDelegation,\n type HostedHeartbeatDelegationRequest,\n} from './contracts.js';\nimport { createHostedRuntimeSessionId } from './runtime-session.js';\nimport type {\n HostedHeartbeatDelegationIssuer,\n HostedHeartbeatDelegationServiceConfig,\n} from './types.js';\n\nconst DelegationServiceConfigSchema = z.object({\n runtimeSessionNamespace: OpaqueIdSchema,\n maxExecutionMs: z.number().int().min(1_000).max(Number.MAX_SAFE_INTEGER),\n}).strict();\n\nexport class HostedHeartbeatDelegationRejectedError extends Error {\n readonly name = 'HostedHeartbeatDelegationRejectedError';\n}\n\n/**\n * Converts one product authorization decision into a fully bound Heddle\n * authority bundle. Product code never constructs the delegation wire shape.\n */\nexport class HostedHeartbeatDelegationService\nimplements HostedHeartbeatDelegationIssuer {\n readonly #authority: HostedHeartbeatDelegationServiceConfig['authority'];\n readonly #authorizer: HostedHeartbeatDelegationServiceConfig['authorizer'];\n readonly #runtimeSessionNamespace: string;\n readonly #maxExecutionMs: number;\n readonly #now: () => Date;\n\n constructor(config: HostedHeartbeatDelegationServiceConfig) {\n const parsed = DelegationServiceConfigSchema.parse({\n runtimeSessionNamespace: config.runtimeSessionNamespace,\n maxExecutionMs: config.maxExecutionMs,\n });\n this.#authority = config.authority;\n this.#authorizer = config.authorizer;\n this.#runtimeSessionNamespace = parsed.runtimeSessionNamespace;\n this.#maxExecutionMs = parsed.maxExecutionMs;\n this.#now = config.now ?? (() => new Date());\n }\n\n async issue(\n rawInput: HostedHeartbeatDelegationRequest,\n rawSignal?: AbortSignal,\n ): Promise<HostedHeartbeatDelegation> {\n const input = HostedHeartbeatDelegationRequestSchema.parse(rawInput);\n const signal = rawSignal ?? new AbortController().signal;\n signal.throwIfAborted();\n const rawAuthorization = await this.#authorizer.authorize({\n taskId: input.taskId,\n executionId: input.executionId,\n signal,\n });\n if (!rawAuthorization) {\n throw new HostedHeartbeatDelegationRejectedError(\n 'The product rejected this heartbeat execution.',\n );\n }\n const authorization = HostedHeartbeatDelegationAuthorizationSchema.parse(\n rawAuthorization,\n );\n const runtimeSessionId = createHostedRuntimeSessionId({\n namespace: this.#runtimeSessionNamespace,\n scope: authorization.scope,\n });\n const deadlineAt = dayjs(this.#now())\n .add(this.#maxExecutionMs, 'millisecond')\n .toISOString();\n const issued = await this.#authority.issue({\n scope: authorization.scope,\n runtimeSessionId,\n invocationId: input.executionId,\n workflow: HEARTBEAT_TASK_WORKFLOW,\n mcp: { allowedTools: authorization.allowedTools },\n });\n signal.throwIfAborted();\n const mcpCapability = issued.mcpCapability();\n if (!mcpCapability || !issued.metadata.mcp) {\n throw new Error(\n 'Hosted heartbeat delegation requires an MCP-capable execution authority.',\n );\n }\n\n return HostedHeartbeatDelegationSchema.parse({\n schemaVersion: 1,\n taskId: input.taskId,\n executionId: input.executionId,\n scope: authorization.scope,\n runtimeSessionId,\n deadlineAt,\n authority: {\n metadata: issued.metadata,\n executionAssertion: issued.executionAssertion(),\n mcpCapability,\n },\n });\n }\n}\n"]}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
import type { NodeHostedHeartbeatDelegationHttpHandler, NodeHostedHeartbeatDelegationHttpServiceConfig } from './types.js';
|
|
3
|
-
/** Standard authenticated Node HTTP edge for product-owned run authority. */
|
|
4
|
-
export declare class NodeHostedHeartbeatDelegationHttpService implements NodeHostedHeartbeatDelegationHttpHandler {
|
|
5
|
-
#private;
|
|
6
|
-
constructor(config: NodeHostedHeartbeatDelegationHttpServiceConfig);
|
|
7
|
-
handle(request: IncomingMessage, response: ServerResponse): boolean;
|
|
8
|
-
handleDelegation(request: IncomingMessage, response: ServerResponse): void;
|
|
9
|
-
close(): Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
//# sourceMappingURL=delegation-http-service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"delegation-http-service.d.ts","sourceRoot":"","sources":["../../../src/coordinator/node/delegation-http-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAoBjE,OAAO,KAAK,EAEV,wCAAwC,EACxC,8CAA8C,EAC/C,MAAM,YAAY,CAAC;AAWpB,6EAA6E;AAC7E,qBAAa,wCACb,YAAW,wCAAwC;;gBAcrC,MAAM,EAAE,8CAA8C;IAclE,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,GAAG,OAAO;IAQnE,gBAAgB,CACd,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,GACvB,IAAI;IA0DP,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAkEvB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"delegation-http-service.js","sourceRoot":"","sources":["../../../src/coordinator/node/delegation-http-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, timingSafeEqual } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { z, ZodError } from 'zod';\nimport {\n HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n HostedHeartbeatDelegationRequestSchema,\n} from '../contracts.js';\nimport {\n HostedHeartbeatDelegationRejectedError,\n} from '../hosted-heartbeat-delegation-service.js';\nimport { HostedHeartbeatServiceTokenSchema } from '../service-token.js';\nimport {\n NodeHttpPathSchema,\n NodeHttpRequestError,\n errorType,\n readJsonBody,\n readPathname,\n takeAuthorization,\n writeJson,\n writeJsonError,\n} from '../../node/http-utils.js';\nimport type {\n NodeHostedHeartbeatDelegationFailure,\n NodeHostedHeartbeatDelegationHttpHandler,\n NodeHostedHeartbeatDelegationHttpServiceConfig,\n} from './types.js';\n\nconst DEFAULT_MAX_BODY_BYTES = 16 * 1_024;\n\ntype ActiveDelegation = {\n abortController: AbortController;\n request: IncomingMessage;\n response: ServerResponse;\n pending: Promise<void>;\n};\n\n/** Standard authenticated Node HTTP edge for product-owned run authority. */\nexport class NodeHostedHeartbeatDelegationHttpService\nimplements NodeHostedHeartbeatDelegationHttpHandler {\n readonly #delegations: NodeHostedHeartbeatDelegationHttpServiceConfig[\n 'delegations'\n ];\n readonly #tokenDigest: Buffer;\n readonly #path: string;\n readonly #maxBodyBytes: number;\n readonly #reportFailure: NodeHostedHeartbeatDelegationHttpServiceConfig[\n 'reportFailure'\n ];\n readonly #active = new Set<ActiveDelegation>();\n #closed = false;\n #closePromise: Promise<void> | undefined;\n\n constructor(config: NodeHostedHeartbeatDelegationHttpServiceConfig) {\n this.#delegations = config.delegations;\n this.#tokenDigest = digest(\n HostedHeartbeatServiceTokenSchema.parse(config.apiToken),\n );\n this.#path = NodeHttpPathSchema.parse(\n config.path ?? HOSTED_HEARTBEAT_DELEGATIONS_PATH,\n );\n this.#maxBodyBytes = z.number().int().min(1).max(1_048_576).parse(\n config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES,\n );\n this.#reportFailure = config.reportFailure;\n }\n\n handle(request: IncomingMessage, response: ServerResponse): boolean {\n if (readPathname(request.url) !== this.#path) {\n return false;\n }\n this.handleDelegation(request, response);\n return true;\n }\n\n handleDelegation(\n request: IncomingMessage,\n response: ServerResponse,\n ): void {\n if (this.#closed) {\n request.resume();\n writeJsonError(response, 503, 'Hosted execution is unavailable.');\n return;\n }\n\n let authorization: string | undefined;\n try {\n authorization = takeAuthorization(request);\n if (request.method !== 'POST') {\n request.resume();\n writeJsonError(response, 405, 'Method not allowed.', {\n Allow: 'POST',\n });\n return;\n }\n } catch (error) {\n request.resume();\n writeJsonError(\n response,\n error instanceof NodeHttpRequestError ? error.statusCode : 400,\n 'Invalid heartbeat delegation request.',\n );\n return;\n }\n if (!this.#authenticates(authorization)) {\n request.resume();\n writeJsonError(response, 401, 'Authentication is required.', {\n 'WWW-Authenticate': 'Bearer',\n });\n return;\n }\n\n const abortController = new AbortController();\n const abort = () => abortController.abort(\n new Error('The heartbeat delegation request closed.'),\n );\n request.once('aborted', abort);\n response.once('close', abort);\n const active: ActiveDelegation = {\n abortController,\n request,\n response,\n pending: Promise.resolve(),\n };\n active.pending = this.#serve(\n request,\n response,\n abortController.signal,\n ).finally(() => {\n request.removeListener('aborted', abort);\n response.removeListener('close', abort);\n this.#active.delete(active);\n });\n this.#active.add(active);\n }\n\n close(): Promise<void> {\n if (!this.#closePromise) {\n this.#closed = true;\n const active = [...this.#active];\n active.forEach(({ abortController, request, response }) => {\n abortController.abort(\n new Error('The heartbeat delegation service is stopping.'),\n );\n request.destroy();\n response.destroy();\n });\n this.#closePromise = Promise.allSettled(\n active.map(({ pending }) => pending),\n ).then(() => undefined);\n }\n return this.#closePromise;\n }\n\n async #serve(\n request: IncomingMessage,\n response: ServerResponse,\n signal: AbortSignal,\n ): Promise<void> {\n try {\n const input = HostedHeartbeatDelegationRequestSchema.parse(\n await readJsonBody(request, this.#maxBodyBytes, signal),\n );\n const delegation = await this.#delegations.issue(input, signal);\n writeJson(response, 200, delegation);\n } catch (error) {\n if (signal.aborted) {\n return;\n }\n if (error instanceof HostedHeartbeatDelegationRejectedError) {\n writeJsonError(response, 403, error.message);\n return;\n }\n if (error instanceof NodeHttpRequestError || error instanceof ZodError) {\n writeJsonError(\n response,\n error instanceof NodeHttpRequestError ? error.statusCode : 400,\n 'Invalid heartbeat delegation request.',\n );\n return;\n }\n this.#report({ phase: 'delegation', errorType: errorType(error) });\n writeJsonError(response, 500, 'Heartbeat delegation failed.');\n }\n }\n\n #authenticates(authorization: string | undefined): boolean {\n const token = /^Bearer ([^\\s]+)$/i.exec(\n authorization?.trim() ?? '',\n )?.[1];\n return token\n ? timingSafeEqual(digest(token), this.#tokenDigest)\n : false;\n }\n\n #report(failure: NodeHostedHeartbeatDelegationFailure): void {\n try {\n this.#reportFailure?.(Object.freeze({ ...failure }));\n } catch {\n // Observability must not change request settlement.\n }\n }\n}\n\nfunction digest(value: string): Buffer {\n return createHash('sha256').update(value, 'utf8').digest();\n}\n"]}
|