@tarkahq/sdk 0.0.0-stage → 0.3.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/LICENSE +201 -0
- package/NOTICE +69 -0
- package/README.md +700 -2
- package/api-source.json +38 -0
- package/dist/cjs/client.d.ts +114 -0
- package/dist/cjs/client.js +174 -0
- package/dist/cjs/errors.d.ts +27 -0
- package/dist/cjs/errors.js +78 -0
- package/dist/cjs/generated/google/api/annotations.d.ts +1 -0
- package/dist/cjs/generated/google/api/annotations.js +10 -0
- package/dist/cjs/generated/google/api/http.d.ts +395 -0
- package/dist/cjs/generated/google/api/http.js +421 -0
- package/dist/cjs/generated/google/protobuf/descriptor.d.ts +873 -0
- package/dist/cjs/generated/google/protobuf/descriptor.js +3992 -0
- package/dist/cjs/generated/google/protobuf/empty.d.ts +35 -0
- package/dist/cjs/generated/google/protobuf/empty.js +58 -0
- package/dist/cjs/generated/google/protobuf/struct.d.ts +127 -0
- package/dist/cjs/generated/google/protobuf/struct.js +529 -0
- package/dist/cjs/generated/google/protobuf/timestamp.d.ts +128 -0
- package/dist/cjs/generated/google/protobuf/timestamp.js +95 -0
- package/dist/cjs/generated/protoc-gen-openapiv2/options/annotations.d.ts +1 -0
- package/dist/cjs/generated/protoc-gen-openapiv2/options/annotations.js +10 -0
- package/dist/cjs/generated/protoc-gen-openapiv2/options/openapiv2.d.ts +983 -0
- package/dist/cjs/generated/protoc-gen-openapiv2/options/openapiv2.js +4874 -0
- package/dist/cjs/generated/tarka/access/v1/access.d.ts +322 -0
- package/dist/cjs/generated/tarka/access/v1/access.js +2979 -0
- package/dist/cjs/generated/tarka/channels/v1/channels.d.ts +370 -0
- package/dist/cjs/generated/tarka/channels/v1/channels.js +3603 -0
- package/dist/cjs/generated/tarka/gateway/v1/gateway.d.ts +804 -0
- package/dist/cjs/generated/tarka/gateway/v1/gateway.js +8081 -0
- package/dist/cjs/generated/tarka/iam/v1/iam.d.ts +1291 -0
- package/dist/cjs/generated/tarka/iam/v1/iam.js +13207 -0
- package/dist/cjs/generated/tarka/inference/v1/inference.d.ts +699 -0
- package/dist/cjs/generated/tarka/inference/v1/inference.js +7058 -0
- package/dist/cjs/generated/tarka/provisioning/v1/agent_secrets.d.ts +70 -0
- package/dist/cjs/generated/tarka/provisioning/v1/agent_secrets.js +603 -0
- package/dist/cjs/generated/tarka/provisioning/v1/billing_quote.d.ts +171 -0
- package/dist/cjs/generated/tarka/provisioning/v1/billing_quote.js +1588 -0
- package/dist/cjs/generated/tarka/provisioning/v1/job.d.ts +168 -0
- package/dist/cjs/generated/tarka/provisioning/v1/job.js +1505 -0
- package/dist/cjs/generated/tarka/provisioning/v1/provisioning.d.ts +2644 -0
- package/dist/cjs/generated/tarka/provisioning/v1/provisioning.js +19501 -0
- package/dist/cjs/generated/tarka/provisioning/v1/registry_credentials.d.ts +89 -0
- package/dist/cjs/generated/tarka/provisioning/v1/registry_credentials.js +856 -0
- package/dist/cjs/generated/tarka/provisioning/v1/sandbox.d.ts +431 -0
- package/dist/cjs/generated/tarka/provisioning/v1/sandbox.js +3203 -0
- package/dist/cjs/generated/tarka/provisioning/v1/source_builds.d.ts +291 -0
- package/dist/cjs/generated/tarka/provisioning/v1/source_builds.js +3920 -0
- package/dist/cjs/grpc.d.ts +12 -0
- package/dist/cjs/grpc.js +55 -0
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.js +21 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/resources/inference.d.ts +293 -0
- package/dist/cjs/resources/inference.js +451 -0
- package/dist/cjs/resources/provisioning.d.ts +389 -0
- package/dist/cjs/resources/provisioning.js +552 -0
- package/dist/cjs/resources/sandboxes.d.ts +360 -0
- package/dist/cjs/resources/sandboxes.js +732 -0
- package/dist/cjs/rpc-types.d.ts +36 -0
- package/dist/cjs/rpc-types.js +29 -0
- package/dist/cjs/runtime.d.ts +99 -0
- package/dist/cjs/runtime.js +473 -0
- package/dist/cjs/transport.d.ts +60 -0
- package/dist/cjs/transport.js +114 -0
- package/dist/cjs/types.d.ts +309 -0
- package/dist/cjs/types.js +2 -0
- package/dist/esm/client.d.ts +114 -0
- package/dist/esm/client.js +169 -0
- package/dist/esm/errors.d.ts +27 -0
- package/dist/esm/errors.js +71 -0
- package/dist/esm/generated/google/api/annotations.d.ts +1 -0
- package/dist/esm/generated/google/api/annotations.js +7 -0
- package/dist/esm/generated/google/api/http.d.ts +395 -0
- package/dist/esm/generated/google/api/http.js +418 -0
- package/dist/esm/generated/google/protobuf/descriptor.d.ts +873 -0
- package/dist/esm/generated/google/protobuf/descriptor.js +3977 -0
- package/dist/esm/generated/google/protobuf/empty.d.ts +35 -0
- package/dist/esm/generated/google/protobuf/empty.js +55 -0
- package/dist/esm/generated/google/protobuf/struct.d.ts +127 -0
- package/dist/esm/generated/google/protobuf/struct.js +524 -0
- package/dist/esm/generated/google/protobuf/timestamp.d.ts +128 -0
- package/dist/esm/generated/google/protobuf/timestamp.js +92 -0
- package/dist/esm/generated/protoc-gen-openapiv2/options/annotations.d.ts +1 -0
- package/dist/esm/generated/protoc-gen-openapiv2/options/annotations.js +7 -0
- package/dist/esm/generated/protoc-gen-openapiv2/options/openapiv2.d.ts +983 -0
- package/dist/esm/generated/protoc-gen-openapiv2/options/openapiv2.js +4859 -0
- package/dist/esm/generated/tarka/access/v1/access.d.ts +322 -0
- package/dist/esm/generated/tarka/access/v1/access.js +2976 -0
- package/dist/esm/generated/tarka/channels/v1/channels.d.ts +370 -0
- package/dist/esm/generated/tarka/channels/v1/channels.js +3600 -0
- package/dist/esm/generated/tarka/gateway/v1/gateway.d.ts +804 -0
- package/dist/esm/generated/tarka/gateway/v1/gateway.js +8077 -0
- package/dist/esm/generated/tarka/iam/v1/iam.d.ts +1291 -0
- package/dist/esm/generated/tarka/iam/v1/iam.js +13203 -0
- package/dist/esm/generated/tarka/inference/v1/inference.d.ts +699 -0
- package/dist/esm/generated/tarka/inference/v1/inference.js +7055 -0
- package/dist/esm/generated/tarka/provisioning/v1/agent_secrets.d.ts +70 -0
- package/dist/esm/generated/tarka/provisioning/v1/agent_secrets.js +600 -0
- package/dist/esm/generated/tarka/provisioning/v1/billing_quote.d.ts +171 -0
- package/dist/esm/generated/tarka/provisioning/v1/billing_quote.js +1585 -0
- package/dist/esm/generated/tarka/provisioning/v1/job.d.ts +168 -0
- package/dist/esm/generated/tarka/provisioning/v1/job.js +1500 -0
- package/dist/esm/generated/tarka/provisioning/v1/provisioning.d.ts +2644 -0
- package/dist/esm/generated/tarka/provisioning/v1/provisioning.js +19496 -0
- package/dist/esm/generated/tarka/provisioning/v1/registry_credentials.d.ts +89 -0
- package/dist/esm/generated/tarka/provisioning/v1/registry_credentials.js +853 -0
- package/dist/esm/generated/tarka/provisioning/v1/sandbox.d.ts +431 -0
- package/dist/esm/generated/tarka/provisioning/v1/sandbox.js +3192 -0
- package/dist/esm/generated/tarka/provisioning/v1/source_builds.d.ts +291 -0
- package/dist/esm/generated/tarka/provisioning/v1/source_builds.js +3917 -0
- package/dist/esm/grpc.d.ts +12 -0
- package/dist/esm/grpc.js +11 -0
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/resources/inference.d.ts +293 -0
- package/dist/esm/resources/inference.js +437 -0
- package/dist/esm/resources/provisioning.d.ts +389 -0
- package/dist/esm/resources/provisioning.js +531 -0
- package/dist/esm/resources/sandboxes.d.ts +360 -0
- package/dist/esm/resources/sandboxes.js +718 -0
- package/dist/esm/rpc-types.d.ts +36 -0
- package/dist/esm/rpc-types.js +26 -0
- package/dist/esm/runtime.d.ts +99 -0
- package/dist/esm/runtime.js +466 -0
- package/dist/esm/transport.d.ts +60 -0
- package/dist/esm/transport.js +110 -0
- package/dist/esm/types.d.ts +309 -0
- package/dist/esm/types.js +1 -0
- package/package.json +155 -4
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ChannelsServiceDefinition } from "./generated/tarka/channels/v1/channels.js";
|
|
2
|
+
import type { AccessServiceDefinition } from "./generated/tarka/access/v1/access.js";
|
|
3
|
+
import type { GatewayServiceDefinition } from "./generated/tarka/gateway/v1/gateway.js";
|
|
4
|
+
import type { IAMServiceDefinition } from "./generated/tarka/iam/v1/iam.js";
|
|
5
|
+
import type { Channel } from "@grpc/grpc-js";
|
|
6
|
+
import type { InferenceServiceDefinition } from "./generated/tarka/inference/v1/inference.js";
|
|
7
|
+
import type { ProvisioningServiceDefinition } from "./generated/tarka/provisioning/v1/provisioning.js";
|
|
8
|
+
import type { SandboxServiceDefinition } from "./generated/tarka/provisioning/v1/sandbox.js";
|
|
9
|
+
import type { ServiceClient, ServiceDefinition } from "./transport.js";
|
|
10
|
+
/** Native inference methods with generated messages and gRPC errors. Access through client.grpc.inference. */
|
|
11
|
+
export type InferenceRpc = ServiceClient<typeof InferenceServiceDefinition>;
|
|
12
|
+
/** Native control plane methods. Access through client.grpc.provisioning. */
|
|
13
|
+
export type ProvisioningRpc = ServiceClient<typeof ProvisioningServiceDefinition>;
|
|
14
|
+
/** Native sandbox methods with explicit organization IDs. Access through client.grpc.sandboxes. */
|
|
15
|
+
export type SandboxRpc = ServiceClient<typeof SandboxServiceDefinition>;
|
|
16
|
+
/** Low-level escape hatch. Resource methods on the client are preferred for normal application use. */
|
|
17
|
+
export type IAMRpc = ServiceClient<typeof IAMServiceDefinition>;
|
|
18
|
+
export type GatewayRpc = ServiceClient<typeof GatewayServiceDefinition>;
|
|
19
|
+
export type AccessRpc = ServiceClient<typeof AccessServiceDefinition>;
|
|
20
|
+
export type ChannelsRpc = ServiceClient<typeof ChannelsServiceDefinition>;
|
|
21
|
+
export interface GrpcClient {
|
|
22
|
+
/** Shared native channel. Client.close() closes it only when the SDK owns it. */
|
|
23
|
+
readonly iam: IAMRpc;
|
|
24
|
+
readonly gateway: GatewayRpc;
|
|
25
|
+
readonly access: AccessRpc;
|
|
26
|
+
readonly channels: ChannelsRpc;
|
|
27
|
+
readonly channel: Channel;
|
|
28
|
+
/** Inference RPCs with generated request/response shapes and numeric status codes. */
|
|
29
|
+
readonly inference: InferenceRpc;
|
|
30
|
+
/** Control plane RPCs with explicit request envelopes and organization IDs. */
|
|
31
|
+
readonly provisioning: ProvisioningRpc;
|
|
32
|
+
/** Sandbox RPCs with raw enum values and explicit idempotency-key handling. */
|
|
33
|
+
readonly sandboxes: SandboxRpc;
|
|
34
|
+
}
|
|
35
|
+
/** Keep native errors on client.grpc, and normalize errors only in the SDK layer. */
|
|
36
|
+
export declare function sdkService<D extends ServiceDefinition>(definition: D, client: ServiceClient<D>): ServiceClient<D>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sdkService = sdkService;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
/** Keep native errors on client.grpc, and normalize errors only in the SDK layer. */
|
|
6
|
+
function sdkService(definition, client) {
|
|
7
|
+
const result = {};
|
|
8
|
+
for (const [key, method] of Object.entries(definition.methods)) {
|
|
9
|
+
const invoke = client[key];
|
|
10
|
+
result[key] = method.responseStream
|
|
11
|
+
? async function* (...args) {
|
|
12
|
+
try {
|
|
13
|
+
yield* invoke(...args);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw error instanceof TypeError ? error : (0, errors_js_1.normalizeError)(error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
: async (...args) => {
|
|
20
|
+
try {
|
|
21
|
+
return await invoke(...args);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw error instanceof TypeError ? error : (0, errors_js_1.normalizeError)(error);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return Object.freeze(result);
|
|
29
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { inspect } from "node:util";
|
|
2
|
+
export interface RuntimePermission {
|
|
3
|
+
readonly resource: string;
|
|
4
|
+
readonly action: "invoke" | "read" | "get";
|
|
5
|
+
}
|
|
6
|
+
export interface RuntimeStart {
|
|
7
|
+
readonly gateway_id: string;
|
|
8
|
+
readonly transport: "api" | "mcp";
|
|
9
|
+
readonly permissions: readonly RuntimePermission[];
|
|
10
|
+
readonly max_seconds?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RuntimeMetadata {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly realm_id: string;
|
|
15
|
+
readonly subject_id: string;
|
|
16
|
+
readonly actor_id: string;
|
|
17
|
+
readonly grant_id: string;
|
|
18
|
+
readonly version: string;
|
|
19
|
+
readonly status: string;
|
|
20
|
+
readonly gateway_id: string;
|
|
21
|
+
readonly transport: "api" | "mcp";
|
|
22
|
+
readonly permissions: readonly RuntimePermission[];
|
|
23
|
+
readonly expires_at: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RuntimeClientOptions {
|
|
26
|
+
/** Canonical HTTPS gateway origin, from trusted application configuration. */
|
|
27
|
+
readonly origin: string;
|
|
28
|
+
readonly realmId: string;
|
|
29
|
+
/** Obtain a fresh OAuth token for runtime.execute and the exact runtime audience. */
|
|
30
|
+
readonly tokenProvider: () => string | Promise<string>;
|
|
31
|
+
readonly timeoutMs?: number;
|
|
32
|
+
/** Trusted transport hook, primarily for controlled fixtures. No automatic retries. */
|
|
33
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
34
|
+
}
|
|
35
|
+
/** Errors contain status and a safe local code, never a token or server response body. */
|
|
36
|
+
export declare class RuntimeError extends Error {
|
|
37
|
+
readonly code: string;
|
|
38
|
+
readonly status?: number | undefined;
|
|
39
|
+
constructor(code: string, status?: number | undefined);
|
|
40
|
+
}
|
|
41
|
+
type Lease = {
|
|
42
|
+
run: RuntimeMetadata;
|
|
43
|
+
credential: string;
|
|
44
|
+
expires_at: string;
|
|
45
|
+
audience: string;
|
|
46
|
+
};
|
|
47
|
+
/** Keep this client and its token provider outside the model/tool execution context. */
|
|
48
|
+
export declare class RuntimeClient {
|
|
49
|
+
#private;
|
|
50
|
+
readonly audience: string;
|
|
51
|
+
constructor(options: RuntimeClientOptions);
|
|
52
|
+
toJSON(): {
|
|
53
|
+
audience: string;
|
|
54
|
+
};
|
|
55
|
+
[inspect.custom](): string;
|
|
56
|
+
start(request: RuntimeStart): Promise<RuntimeRun>;
|
|
57
|
+
/** Claim one verified message with autonomous worker authority. Never retries. */
|
|
58
|
+
claimMessage(channelId: string, signal?: AbortSignal): Promise<RuntimeMessage | null>;
|
|
59
|
+
/** Only successfully empty polls repeat. Uncertain claims/replies propagate. */
|
|
60
|
+
messages(channelId: string, options?: {
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
pollIntervalMs?: number;
|
|
63
|
+
}): AsyncGenerator<RuntimeMessage>;
|
|
64
|
+
/** Finishes on success, cancels on callback failure. Cleanup failure never hides the callback error. */
|
|
65
|
+
withRun<T>(input: RuntimeStart, callback: (run: RuntimeRun) => Promise<T>): Promise<T>;
|
|
66
|
+
}
|
|
67
|
+
type Operations = {
|
|
68
|
+
renew: (run: RuntimeMetadata) => Promise<Lease>;
|
|
69
|
+
finish: (run: RuntimeMetadata, token: string, state: string) => Promise<void>;
|
|
70
|
+
send: (url: string, token: string, init: RequestInit) => Promise<Response>;
|
|
71
|
+
reply?: (run: RuntimeMetadata, token: string, text: string) => Promise<void>;
|
|
72
|
+
};
|
|
73
|
+
/** A trusted harness handle, not a model tool argument. No parent or upstream secrets are exposed. */
|
|
74
|
+
export declare class RuntimeRun {
|
|
75
|
+
#private;
|
|
76
|
+
readonly audience: string;
|
|
77
|
+
constructor(lease: Lease, operations: Operations);
|
|
78
|
+
get metadata(): RuntimeMetadata;
|
|
79
|
+
toJSON(): {
|
|
80
|
+
run: RuntimeMetadata;
|
|
81
|
+
audience: string;
|
|
82
|
+
};
|
|
83
|
+
[inspect.custom](): string;
|
|
84
|
+
renew(): Promise<void>;
|
|
85
|
+
finish(state?: "completed" | "failed" | "cancelled"): Promise<void>;
|
|
86
|
+
/** Reply once to a claimed message and finish; no caller-selected destination. */
|
|
87
|
+
reply(text: string): Promise<void>;
|
|
88
|
+
/** Invoke only a relative API route within this run's gateway. Never retries an operation. */
|
|
89
|
+
api(path: string, init?: RequestInit): Promise<Response>;
|
|
90
|
+
/** Fetch adapter for an MCP Streamable HTTP transport, bound to this exact MCP URL. */
|
|
91
|
+
readonly mcpFetch: typeof globalThis.fetch;
|
|
92
|
+
}
|
|
93
|
+
/** Only prompt is model input. Keep this handle and its transports in the harness. */
|
|
94
|
+
export declare class RuntimeMessage extends RuntimeRun {
|
|
95
|
+
#private;
|
|
96
|
+
constructor(lease: Lease, operations: Operations, prompt: string);
|
|
97
|
+
get prompt(): string;
|
|
98
|
+
}
|
|
99
|
+
export {};
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeMessage = exports.RuntimeRun = exports.RuntimeClient = exports.RuntimeError = void 0;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
const promises_1 = require("node:timers/promises");
|
|
6
|
+
/** Errors contain status and a safe local code, never a token or server response body. */
|
|
7
|
+
class RuntimeError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
status;
|
|
10
|
+
constructor(code, status) {
|
|
11
|
+
super(code);
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.name = "RuntimeError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.RuntimeError = RuntimeError;
|
|
18
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
19
|
+
const id = (value) => typeof value === "string" &&
|
|
20
|
+
uuid.test(value) &&
|
|
21
|
+
!/^0{8}-0{4}-0{4}-0{4}-0{12}$/.test(value);
|
|
22
|
+
const credential = /^tr_run_[A-Za-z0-9_-]{43}$/;
|
|
23
|
+
const secret = (value) => typeof value === "string" &&
|
|
24
|
+
value.length >= 16 &&
|
|
25
|
+
value.length <= 8192 &&
|
|
26
|
+
!/\s/.test(value);
|
|
27
|
+
function validateStart(input) {
|
|
28
|
+
if (!id(input.gateway_id) ||
|
|
29
|
+
!["api", "mcp"].includes(input.transport) ||
|
|
30
|
+
!Array.isArray(input.permissions) ||
|
|
31
|
+
input.permissions.length < 1 ||
|
|
32
|
+
input.permissions.length > 64 ||
|
|
33
|
+
input.permissions.some((p) => !id(p.resource) || !["invoke", "read", "get"].includes(p.action)) ||
|
|
34
|
+
new Set(input.permissions.map((p) => `${p.resource}/${p.action}`)).size !==
|
|
35
|
+
input.permissions.length ||
|
|
36
|
+
!Number.isInteger(input.max_seconds ?? 900) ||
|
|
37
|
+
(input.max_seconds ?? 900) < 1 ||
|
|
38
|
+
(input.max_seconds ?? 900) > 3600)
|
|
39
|
+
throw new RuntimeError("invalid_runtime_request");
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
gateway_id: input.gateway_id,
|
|
42
|
+
transport: input.transport,
|
|
43
|
+
permissions: Object.freeze(input.permissions.map((p) => Object.freeze({ resource: p.resource, action: p.action }))),
|
|
44
|
+
max_seconds: input.max_seconds ?? 900,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function readJSON(response, limit = 32768) {
|
|
48
|
+
const reader = response.body?.getReader();
|
|
49
|
+
if (!reader)
|
|
50
|
+
throw new RuntimeError("invalid_runtime_response");
|
|
51
|
+
const chunks = [];
|
|
52
|
+
let size = 0;
|
|
53
|
+
try {
|
|
54
|
+
while (true) {
|
|
55
|
+
const next = await reader.read();
|
|
56
|
+
if (next.done)
|
|
57
|
+
break;
|
|
58
|
+
size += next.value.length;
|
|
59
|
+
if (size > limit)
|
|
60
|
+
throw new RuntimeError("invalid_runtime_response");
|
|
61
|
+
chunks.push(next.value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
throw new RuntimeError("invalid_runtime_response");
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await reader.cancel().catch(() => { });
|
|
69
|
+
reader.releaseLock();
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(Buffer.concat(chunks).toString("utf8"));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
throw new RuntimeError("invalid_runtime_response");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function readLease(response, realm, audience, input, previous) {
|
|
79
|
+
try {
|
|
80
|
+
const lease = (await readJSON(response));
|
|
81
|
+
const run = lease.run;
|
|
82
|
+
if (lease.audience !== audience ||
|
|
83
|
+
!credential.test(lease.credential) ||
|
|
84
|
+
!id(run.id) ||
|
|
85
|
+
run.realm_id !== realm ||
|
|
86
|
+
run.gateway_id !== input.gateway_id ||
|
|
87
|
+
run.transport !== input.transport ||
|
|
88
|
+
run.status !== "running" ||
|
|
89
|
+
!id(run.subject_id) ||
|
|
90
|
+
(run.actor_id !== "" && !id(run.actor_id)) ||
|
|
91
|
+
(run.grant_id !== "" && !id(run.grant_id)) ||
|
|
92
|
+
typeof run.version !== "string" ||
|
|
93
|
+
!/^[1-9][0-9]*$/.test(run.version) ||
|
|
94
|
+
!Number.isFinite(Date.parse(run.expires_at)) ||
|
|
95
|
+
!Number.isFinite(Date.parse(lease.expires_at)) ||
|
|
96
|
+
Date.parse(lease.expires_at) <= Date.now() ||
|
|
97
|
+
Date.parse(lease.expires_at) > Date.parse(run.expires_at) ||
|
|
98
|
+
!Array.isArray(run.permissions) ||
|
|
99
|
+
run.permissions.length !== input.permissions.length ||
|
|
100
|
+
run.permissions.some((p, i) => p.resource !== input.permissions[i]?.resource ||
|
|
101
|
+
p.action !== input.permissions[i]?.action) ||
|
|
102
|
+
(previous &&
|
|
103
|
+
(run.id !== previous.id ||
|
|
104
|
+
run.subject_id !== previous.subject_id ||
|
|
105
|
+
run.actor_id !== previous.actor_id ||
|
|
106
|
+
run.grant_id !== previous.grant_id ||
|
|
107
|
+
BigInt(run.version) !== BigInt(previous.version) + 1n))) {
|
|
108
|
+
throw new Error();
|
|
109
|
+
}
|
|
110
|
+
lease.run = Object.freeze({
|
|
111
|
+
id: run.id,
|
|
112
|
+
realm_id: run.realm_id,
|
|
113
|
+
subject_id: run.subject_id,
|
|
114
|
+
actor_id: run.actor_id,
|
|
115
|
+
grant_id: run.grant_id,
|
|
116
|
+
version: run.version,
|
|
117
|
+
status: run.status,
|
|
118
|
+
gateway_id: run.gateway_id,
|
|
119
|
+
transport: run.transport,
|
|
120
|
+
expires_at: run.expires_at,
|
|
121
|
+
permissions: input.permissions,
|
|
122
|
+
});
|
|
123
|
+
return lease;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw new RuntimeError("invalid_runtime_response");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Keep this client and its token provider outside the model/tool execution context. */
|
|
130
|
+
class RuntimeClient {
|
|
131
|
+
audience;
|
|
132
|
+
#realm;
|
|
133
|
+
#origin;
|
|
134
|
+
#provider;
|
|
135
|
+
#fetch;
|
|
136
|
+
#timeout;
|
|
137
|
+
constructor(options) {
|
|
138
|
+
let url;
|
|
139
|
+
try {
|
|
140
|
+
url = new URL(options.origin);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new RuntimeError("invalid_runtime_origin");
|
|
144
|
+
}
|
|
145
|
+
if (url.protocol !== "https:" ||
|
|
146
|
+
url.origin !== options.origin ||
|
|
147
|
+
!id(options.realmId) ||
|
|
148
|
+
!Number.isFinite(options.timeoutMs ?? 30000) ||
|
|
149
|
+
(options.timeoutMs ?? 30000) <= 0) {
|
|
150
|
+
throw new RuntimeError("invalid_runtime_configuration");
|
|
151
|
+
}
|
|
152
|
+
this.#origin = options.origin;
|
|
153
|
+
this.#realm = options.realmId;
|
|
154
|
+
this.audience = `${options.origin}/v1/realms/${options.realmId}/runtime`;
|
|
155
|
+
this.#provider = options.tokenProvider;
|
|
156
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
157
|
+
this.#timeout = options.timeoutMs ?? 30000;
|
|
158
|
+
}
|
|
159
|
+
toJSON() {
|
|
160
|
+
return { audience: this.audience };
|
|
161
|
+
}
|
|
162
|
+
[node_util_1.inspect.custom]() {
|
|
163
|
+
return `RuntimeClient(${this.audience})`;
|
|
164
|
+
}
|
|
165
|
+
async #parent() {
|
|
166
|
+
let token;
|
|
167
|
+
try {
|
|
168
|
+
token = await this.#provider();
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
throw new RuntimeError("runtime_identity_unavailable");
|
|
172
|
+
}
|
|
173
|
+
if (!secret(token) || token.startsWith("tr_run_"))
|
|
174
|
+
throw new RuntimeError("invalid_runtime_identity");
|
|
175
|
+
return token;
|
|
176
|
+
}
|
|
177
|
+
async #send(url, token, init) {
|
|
178
|
+
const headers = new Headers(init.headers);
|
|
179
|
+
for (const h of [...headers.keys()]) {
|
|
180
|
+
if (/^(authorization|cookie|proxy-authorization|origin|referer|host|forwarded)$/i.test(h) ||
|
|
181
|
+
/^x-(tarka|forwarded)-/i.test(h))
|
|
182
|
+
headers.delete(h);
|
|
183
|
+
}
|
|
184
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
185
|
+
const timeout = AbortSignal.timeout(this.#timeout);
|
|
186
|
+
const signal = init.signal
|
|
187
|
+
? AbortSignal.any([init.signal, timeout])
|
|
188
|
+
: timeout;
|
|
189
|
+
let response;
|
|
190
|
+
try {
|
|
191
|
+
response = await this.#fetch(url, {
|
|
192
|
+
...init,
|
|
193
|
+
headers,
|
|
194
|
+
redirect: "error",
|
|
195
|
+
credentials: "omit",
|
|
196
|
+
referrerPolicy: "no-referrer",
|
|
197
|
+
signal,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
throw new RuntimeError("runtime_transport_uncertain");
|
|
202
|
+
}
|
|
203
|
+
if (response.status >= 300 && response.status < 400) {
|
|
204
|
+
await response.body?.cancel().catch(() => { });
|
|
205
|
+
throw new RuntimeError("runtime_redirect_rejected", response.status);
|
|
206
|
+
}
|
|
207
|
+
return response;
|
|
208
|
+
}
|
|
209
|
+
async #control(path, token, body) {
|
|
210
|
+
const response = await this.#send(`${this.audience}/runs${path}`, token, {
|
|
211
|
+
method: "POST",
|
|
212
|
+
headers: { "Content-Type": "application/json" },
|
|
213
|
+
body: JSON.stringify(body),
|
|
214
|
+
});
|
|
215
|
+
if (!response.ok) {
|
|
216
|
+
await response.body?.cancel().catch(() => { });
|
|
217
|
+
throw new RuntimeError("runtime_request_rejected", response.status);
|
|
218
|
+
}
|
|
219
|
+
return response;
|
|
220
|
+
}
|
|
221
|
+
async start(request) {
|
|
222
|
+
const input = validateStart(request);
|
|
223
|
+
const audience = `${this.#origin}/v1/realms/${this.#realm}/gateways/${input.gateway_id}${input.transport === "mcp" ? "/mcp" : ""}`;
|
|
224
|
+
const response = await this.#control("", await this.#parent(), input);
|
|
225
|
+
const lease = await readLease(response, this.#realm, audience, input);
|
|
226
|
+
return new RuntimeRun(lease, this.#operations(audience, input));
|
|
227
|
+
}
|
|
228
|
+
#operations(audience, input, channel) {
|
|
229
|
+
return {
|
|
230
|
+
renew: async (run) => readLease(await this.#control(`/${run.id}/renew`, await this.#parent(), {
|
|
231
|
+
expected_version: run.version,
|
|
232
|
+
}), this.#realm, audience, input, run),
|
|
233
|
+
finish: async (run, token, state) => {
|
|
234
|
+
const response = await this.#control(`/${run.id}/finish`, token, {
|
|
235
|
+
state,
|
|
236
|
+
});
|
|
237
|
+
await response.body?.cancel();
|
|
238
|
+
},
|
|
239
|
+
send: (url, token, init) => this.#send(url, token, init),
|
|
240
|
+
...(channel
|
|
241
|
+
? {
|
|
242
|
+
reply: async (run, token, text) => {
|
|
243
|
+
const response = await this.#send(`${this.#origin}/v1/realms/${this.#realm}/channels/${channel}/runs/${run.id}/reply`, token, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: { "Content-Type": "application/json" },
|
|
246
|
+
body: JSON.stringify({ text }),
|
|
247
|
+
});
|
|
248
|
+
await response.body?.cancel().catch(() => { });
|
|
249
|
+
if (response.status !== 204)
|
|
250
|
+
throw new RuntimeError("messaging_request_rejected", response.status);
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
: {}),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** Claim one verified message with autonomous worker authority. Never retries. */
|
|
257
|
+
async claimMessage(channelId, signal) {
|
|
258
|
+
if (!id(channelId))
|
|
259
|
+
throw new RuntimeError("invalid_messaging_channel");
|
|
260
|
+
const response = await this.#send(`${this.#origin}/v1/realms/${this.#realm}/channels/${channelId}/claim`, await this.#parent(), {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: { "Content-Type": "application/json" },
|
|
263
|
+
body: "{}",
|
|
264
|
+
signal,
|
|
265
|
+
});
|
|
266
|
+
if (response.status === 204) {
|
|
267
|
+
await response.body?.cancel().catch(() => { });
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
if (response.status !== 200) {
|
|
271
|
+
await response.body?.cancel().catch(() => { });
|
|
272
|
+
throw new RuntimeError("messaging_request_rejected", response.status);
|
|
273
|
+
}
|
|
274
|
+
let value, input;
|
|
275
|
+
try {
|
|
276
|
+
value = (await readJSON(response, 65536));
|
|
277
|
+
if (typeof value.prompt !== "string" ||
|
|
278
|
+
!value.prompt.trim() ||
|
|
279
|
+
/[\u0000\uD800-\uDFFF]/u.test(value.prompt) ||
|
|
280
|
+
Buffer.byteLength(value.prompt, "utf8") > 16000 ||
|
|
281
|
+
!id(value.run.actor_id) ||
|
|
282
|
+
!id(value.run.grant_id))
|
|
283
|
+
throw new Error();
|
|
284
|
+
input = validateStart({
|
|
285
|
+
gateway_id: value.run.gateway_id,
|
|
286
|
+
transport: value.run.transport,
|
|
287
|
+
permissions: value.run.permissions,
|
|
288
|
+
max_seconds: 3600,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
throw new RuntimeError("invalid_messaging_response");
|
|
293
|
+
}
|
|
294
|
+
const audience = `${this.#origin}/v1/realms/${this.#realm}/gateways/${input.gateway_id}${input.transport === "mcp" ? "/mcp" : ""}`;
|
|
295
|
+
// Reuse the same strict identity, scope, expiry and origin validation as runs.
|
|
296
|
+
const { prompt, ...raw } = value;
|
|
297
|
+
const lease = await readLease(Response.json(raw), this.#realm, audience, input);
|
|
298
|
+
return new RuntimeMessage(lease, this.#operations(audience, input, channelId), prompt);
|
|
299
|
+
}
|
|
300
|
+
/** Only successfully empty polls repeat. Uncertain claims/replies propagate. */
|
|
301
|
+
async *messages(channelId, options = {}) {
|
|
302
|
+
const interval = options.pollIntervalMs ?? 5000;
|
|
303
|
+
if (!Number.isFinite(interval) || interval < 5000)
|
|
304
|
+
throw new RuntimeError("invalid_messaging_poll_interval");
|
|
305
|
+
while (!options.signal?.aborted) {
|
|
306
|
+
const message = await this.claimMessage(channelId, options.signal);
|
|
307
|
+
if (!message) {
|
|
308
|
+
await (0, promises_1.setTimeout)(interval, undefined, { signal: options.signal });
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
if (!options.signal?.aborted)
|
|
313
|
+
yield message;
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
await message.finish("cancelled").catch(() => { });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/** Finishes on success, cancels on callback failure. Cleanup failure never hides the callback error. */
|
|
321
|
+
async withRun(input, callback) {
|
|
322
|
+
const run = await this.start(input);
|
|
323
|
+
let result;
|
|
324
|
+
try {
|
|
325
|
+
result = await callback(run);
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
await run.finish("cancelled").catch(() => { });
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
await run.finish("completed");
|
|
332
|
+
return result;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
exports.RuntimeClient = RuntimeClient;
|
|
336
|
+
/** A trusted harness handle, not a model tool argument. No parent or upstream secrets are exposed. */
|
|
337
|
+
class RuntimeRun {
|
|
338
|
+
audience;
|
|
339
|
+
#lease;
|
|
340
|
+
#ops;
|
|
341
|
+
#queue = Promise.resolve();
|
|
342
|
+
#unusable = false;
|
|
343
|
+
#finished = false;
|
|
344
|
+
constructor(lease, operations) {
|
|
345
|
+
this.#lease = lease;
|
|
346
|
+
this.audience = lease.audience;
|
|
347
|
+
this.#ops = operations;
|
|
348
|
+
}
|
|
349
|
+
get metadata() {
|
|
350
|
+
return this.#lease.run;
|
|
351
|
+
}
|
|
352
|
+
toJSON() {
|
|
353
|
+
return { run: this.metadata, audience: this.audience };
|
|
354
|
+
}
|
|
355
|
+
[node_util_1.inspect.custom]() {
|
|
356
|
+
return `RuntimeRun(${this.metadata.id})`;
|
|
357
|
+
}
|
|
358
|
+
async #exclusive(fn) {
|
|
359
|
+
const previous = this.#queue;
|
|
360
|
+
let release;
|
|
361
|
+
this.#queue = new Promise((resolve) => {
|
|
362
|
+
release = resolve;
|
|
363
|
+
});
|
|
364
|
+
await previous;
|
|
365
|
+
try {
|
|
366
|
+
return await fn();
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
release();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async #renew() {
|
|
373
|
+
if (this.#unusable || this.#finished)
|
|
374
|
+
throw new RuntimeError("runtime_run_unusable");
|
|
375
|
+
try {
|
|
376
|
+
this.#lease = await this.#ops.renew(this.metadata);
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
this.#unusable = true;
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async renew() {
|
|
384
|
+
return this.#exclusive(() => this.#renew());
|
|
385
|
+
}
|
|
386
|
+
async finish(state = "completed") {
|
|
387
|
+
return this.#exclusive(async () => {
|
|
388
|
+
if (this.#finished)
|
|
389
|
+
return;
|
|
390
|
+
if (!["completed", "failed", "cancelled"].includes(state))
|
|
391
|
+
throw new RuntimeError("invalid_runtime_state");
|
|
392
|
+
this.#unusable = true;
|
|
393
|
+
await this.#ops.finish(this.metadata, this.#lease.credential, state);
|
|
394
|
+
this.#finished = true;
|
|
395
|
+
this.#lease = { ...this.#lease, credential: "" };
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
/** Reply once to a claimed message and finish; no caller-selected destination. */
|
|
399
|
+
async reply(text) {
|
|
400
|
+
if (typeof text !== "string" ||
|
|
401
|
+
!text.trim() ||
|
|
402
|
+
/[\u0000\uD800-\uDFFF]/u.test(text) ||
|
|
403
|
+
[...text].length > 2000)
|
|
404
|
+
throw new RuntimeError("invalid_messaging_reply");
|
|
405
|
+
return this.#exclusive(async () => {
|
|
406
|
+
if (this.#unusable || this.#finished)
|
|
407
|
+
throw new RuntimeError("runtime_run_unusable");
|
|
408
|
+
if (!this.#ops.reply)
|
|
409
|
+
throw new RuntimeError("not_a_messaging_run");
|
|
410
|
+
if (Date.parse(this.#lease.expires_at) - Date.now() < 60000)
|
|
411
|
+
await this.#renew();
|
|
412
|
+
this.#unusable = true;
|
|
413
|
+
await this.#ops.reply(this.metadata, this.#lease.credential, text);
|
|
414
|
+
this.#finished = true;
|
|
415
|
+
this.#lease = { ...this.#lease, credential: "" };
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
async #request(url, init) {
|
|
419
|
+
// Serialize through receipt of response headers so a concurrent renewal does
|
|
420
|
+
// not rotate a credential while another request is awaiting admission.
|
|
421
|
+
return this.#exclusive(async () => {
|
|
422
|
+
if (this.#unusable || this.#finished)
|
|
423
|
+
throw new RuntimeError("runtime_run_unusable");
|
|
424
|
+
if (Date.parse(this.#lease.expires_at) - Date.now() < 60000)
|
|
425
|
+
await this.#renew();
|
|
426
|
+
const response = await this.#ops.send(url, this.#lease.credential, init);
|
|
427
|
+
if (response.status === 401 || response.status === 403)
|
|
428
|
+
this.#unusable = true;
|
|
429
|
+
return response;
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
/** Invoke only a relative API route within this run's gateway. Never retries an operation. */
|
|
433
|
+
async api(path, init = {}) {
|
|
434
|
+
if (this.metadata.transport !== "api" ||
|
|
435
|
+
!path.startsWith("/") ||
|
|
436
|
+
path.startsWith("//") ||
|
|
437
|
+
/[\\#]/.test(path))
|
|
438
|
+
throw new RuntimeError("invalid_runtime_destination");
|
|
439
|
+
const base = `${this.audience}/api/`;
|
|
440
|
+
const url = new URL(`${this.audience}/api${path}`);
|
|
441
|
+
if (!url.href.startsWith(base))
|
|
442
|
+
throw new RuntimeError("invalid_runtime_destination");
|
|
443
|
+
return this.#request(url.href, init);
|
|
444
|
+
}
|
|
445
|
+
/** Fetch adapter for an MCP Streamable HTTP transport, bound to this exact MCP URL. */
|
|
446
|
+
mcpFetch = async (input, init) => {
|
|
447
|
+
const request = new Request(input, init);
|
|
448
|
+
if (this.metadata.transport !== "mcp" ||
|
|
449
|
+
request.url !== this.audience ||
|
|
450
|
+
!["POST", "GET", "DELETE"].includes(request.method))
|
|
451
|
+
throw new RuntimeError("invalid_runtime_destination");
|
|
452
|
+
const body = request.body ? await request.arrayBuffer() : undefined;
|
|
453
|
+
return this.#request(request.url, {
|
|
454
|
+
method: request.method,
|
|
455
|
+
headers: request.headers,
|
|
456
|
+
body,
|
|
457
|
+
signal: request.signal,
|
|
458
|
+
});
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
exports.RuntimeRun = RuntimeRun;
|
|
462
|
+
/** Only prompt is model input. Keep this handle and its transports in the harness. */
|
|
463
|
+
class RuntimeMessage extends RuntimeRun {
|
|
464
|
+
#prompt;
|
|
465
|
+
constructor(lease, operations, prompt) {
|
|
466
|
+
super(lease, operations);
|
|
467
|
+
this.#prompt = prompt;
|
|
468
|
+
}
|
|
469
|
+
get prompt() {
|
|
470
|
+
return this.#prompt;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
exports.RuntimeMessage = RuntimeMessage;
|