@tangle-network/agent-provider-cli-bridge 2.0.2 → 2.0.4
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 +33 -0
- package/dist/retained-canonical-event.d.ts +1 -1
- package/dist/retained-canonical-event.js +7 -33
- package/dist/retained-environment.js +2 -3
- package/dist/retained-execution.d.ts +2 -2
- package/dist/retained-execution.js +73 -55
- package/dist/retained-stream.d.ts +2 -0
- package/dist/retained-stream.js +71 -49
- package/dist/sandbox/box.d.ts +60 -0
- package/dist/sandbox/box.js +68 -0
- package/dist/sandbox/image.d.ts +9 -0
- package/dist/sandbox/image.js +31 -0
- package/dist/sandbox/index.d.ts +6 -0
- package/dist/sandbox/index.js +4 -0
- package/dist/sandbox/provider.d.ts +23 -0
- package/dist/sandbox/provider.js +473 -0
- package/dist/sandbox/refusal.d.ts +23 -0
- package/dist/sandbox/refusal.js +159 -0
- package/dist/sandbox/vendor.d.ts +105 -0
- package/dist/sandbox/vendor.js +1 -0
- package/dist/wire.d.ts +4 -1
- package/dist/wire.js +43 -29
- package/package.json +24 -5
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { harnessSystemPromptIntents, } from "@tangle-network/agent-interface";
|
|
2
|
+
import { EnvironmentProviderError } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
+
import { materializeProfile } from "@tangle-network/agent-profile-materialize";
|
|
4
|
+
import { harnessModelSupportFor } from "@tangle-network/cli-agent-registry";
|
|
5
|
+
import { BOX_HARNESSES, boxHarness } from "./box.js";
|
|
6
|
+
/** A profile axis no workspace control enforces here, with the reason it is refused. */
|
|
7
|
+
const REFUSED_PROFILE_AXES = [
|
|
8
|
+
{ path: "profile.confidential", present: (p) => p.confidential !== undefined, reason: "confidential execution needs placement and attestation this provider does not offer" },
|
|
9
|
+
{ path: "profile.seats", present: (p) => p.seats !== undefined, reason: "an ordered subscription chain is not implemented; this provider runs one Router-routed model" },
|
|
10
|
+
{ path: "profile.connections", present: (p) => (p.connections?.length ?? 0) > 0, reason: "connections must be resolved to MCP servers before create" },
|
|
11
|
+
{ path: "profile.proxySecrets", present: (p) => p.proxySecrets !== undefined, reason: "proxy-held credentials are not supported" },
|
|
12
|
+
{ path: "profile.resources.knowledge", present: (p) => Array.isArray(Reflect.get(p.resources ?? {}, "knowledge")) && Reflect.get(p.resources ?? {}, "knowledge").length > 0, reason: "knowledge must be resolved by the runtime before materialization" },
|
|
13
|
+
{ path: "profile.model.maxVisibleOutputTokens", present: (p) => p.model?.maxVisibleOutputTokens !== undefined, reason: "no workspace control enforces an output token cap" },
|
|
14
|
+
{ path: "profile.model.maxReasoningTokens", present: (p) => p.model?.maxReasoningTokens !== undefined, reason: "no workspace control enforces a reasoning token cap" },
|
|
15
|
+
{ path: "profile.model.maxTotalOutputTokens", present: (p) => p.model?.maxTotalOutputTokens !== undefined, reason: "no workspace control enforces a total output token cap" },
|
|
16
|
+
{ path: "profile.extensions", present: (p) => Object.values(p.extensions ?? {}).some((value) => value !== undefined), reason: "no backend extension namespace is implemented" },
|
|
17
|
+
];
|
|
18
|
+
function metadataCredentialSource(profile) {
|
|
19
|
+
const metadata = profile.model?.metadata;
|
|
20
|
+
return metadata !== null && typeof metadata === "object" ? metadata.credentialSource : undefined;
|
|
21
|
+
}
|
|
22
|
+
/** The harness a request selects, or the refusal that names why none can be. */
|
|
23
|
+
export function resolveBoxHarness(profile, backend, declared) {
|
|
24
|
+
const selected = profile.harness ?? backend;
|
|
25
|
+
if (selected === undefined) {
|
|
26
|
+
throw new EnvironmentProviderError("refused", "a profile or backend must name a harness; a prompt is never run as a shell command", { path: "profile.harness" });
|
|
27
|
+
}
|
|
28
|
+
if (profile.harness !== undefined && backend !== undefined && profile.harness !== backend) {
|
|
29
|
+
throw new EnvironmentProviderError("refused", `backend ${backend} conflicts with profile.harness ${profile.harness}`, { path: "backend" });
|
|
30
|
+
}
|
|
31
|
+
if (!declared.includes(selected) || boxHarness(selected) === undefined) {
|
|
32
|
+
throw new EnvironmentProviderError("refused", `harness ${selected} is not in this provider's declared set (${declared.join(", ")})`, { path: backend === undefined ? "profile.harness" : "backend" });
|
|
33
|
+
}
|
|
34
|
+
return selected;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Validate a profile against what the box can run, without any sandbox call.
|
|
38
|
+
*
|
|
39
|
+
* It plans with the same materializer the Bridge in the box uses, so a
|
|
40
|
+
* dimension the planner cannot lower is refused here with its own reason.
|
|
41
|
+
*/
|
|
42
|
+
export function validateBoxProfile(profile, backend, declared) {
|
|
43
|
+
const issues = [];
|
|
44
|
+
const error = (code, path, message) => {
|
|
45
|
+
issues.push({ level: "error", code, path, message });
|
|
46
|
+
};
|
|
47
|
+
let harness;
|
|
48
|
+
try {
|
|
49
|
+
harness = resolveBoxHarness(profile, backend, declared);
|
|
50
|
+
}
|
|
51
|
+
catch (caught) {
|
|
52
|
+
const refusal = caught;
|
|
53
|
+
error("harness_refused", refusal.path ?? "profile.harness", refusal.message);
|
|
54
|
+
}
|
|
55
|
+
for (const axis of REFUSED_PROFILE_AXES) {
|
|
56
|
+
if (axis.present(profile))
|
|
57
|
+
error("axis_refused", axis.path, axis.reason);
|
|
58
|
+
}
|
|
59
|
+
if (metadataCredentialSource(profile) === "subscription") {
|
|
60
|
+
error("subscription_refused", "profile.model.metadata.credentialSource", "subscription credentials are not delivered into a cloud box; the model routes through the Router, and a subscription profile is never run on a managed key");
|
|
61
|
+
}
|
|
62
|
+
if (harness !== undefined) {
|
|
63
|
+
const intents = harnessSystemPromptIntents(harness);
|
|
64
|
+
if (profile.prompt?.systemPrompt !== undefined && !intents.replace)
|
|
65
|
+
error("system_prompt_refused", "profile.prompt.systemPrompt", `${harness} cannot replace its system prompt`);
|
|
66
|
+
if (profile.prompt?.appendSystemPrompt !== undefined && !intents.append)
|
|
67
|
+
error("system_prompt_refused", "profile.prompt.appendSystemPrompt", `${harness} cannot append to its system prompt`);
|
|
68
|
+
const model = profile.model?.default;
|
|
69
|
+
if (model !== undefined) {
|
|
70
|
+
const support = harnessModelSupportFor(harness, { provider: profile.model?.provider, model });
|
|
71
|
+
if (!support.supported)
|
|
72
|
+
error("model_refused", "profile.model.default", support.message);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const plan = materializeProfile(profile, harness);
|
|
76
|
+
for (const entry of plan.unsupported)
|
|
77
|
+
error("unsupported", `profile.${entry.dimension}`, entry.reason);
|
|
78
|
+
}
|
|
79
|
+
catch (caught) {
|
|
80
|
+
error("materialization_refused", "profile", caught instanceof Error ? caught.message : String(caught));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { ok: !issues.some((issue) => issue.level === "error"), issues };
|
|
84
|
+
}
|
|
85
|
+
/** Throw the first refusal of a validation result as a typed error that names its path. */
|
|
86
|
+
export function assertProfileValid(result) {
|
|
87
|
+
const first = result.issues.find((issue) => issue.level === "error");
|
|
88
|
+
if (first !== undefined) {
|
|
89
|
+
throw new EnvironmentProviderError("refused", first.message, { ...(first.path === undefined ? {} : { path: first.path }) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Refuse every create-input field this provider cannot honor, before any
|
|
94
|
+
* vendor call. A field is never accepted and dropped.
|
|
95
|
+
*/
|
|
96
|
+
export function refuseUnsupportedCreateInput(input, support) {
|
|
97
|
+
const refuse = (path, message) => {
|
|
98
|
+
throw new EnvironmentProviderError("refused", message, { path });
|
|
99
|
+
};
|
|
100
|
+
if (typeof input.profile === "string")
|
|
101
|
+
refuse("profile", `named profile ${input.profile} is unsupported; pass an inline AgentProfile`);
|
|
102
|
+
if (input.requestedId !== undefined)
|
|
103
|
+
refuse("requestedId", "the vendor issues sandbox ids");
|
|
104
|
+
if (input.runtimeAttachments !== undefined)
|
|
105
|
+
refuse("runtimeAttachments", "runtime MCP attachments are not supported");
|
|
106
|
+
if (input.idempotencyKey !== undefined)
|
|
107
|
+
refuse("idempotencyKey", "durable keyed create is not implemented");
|
|
108
|
+
if (input.billingOwner !== undefined)
|
|
109
|
+
refuse("billingOwner", "billing owners are not supported");
|
|
110
|
+
if (input.secrets !== undefined)
|
|
111
|
+
refuse("secrets", "secrets are not supported; pass env");
|
|
112
|
+
if (input.proxySecrets !== undefined)
|
|
113
|
+
refuse("proxySecrets", "proxy secrets are not supported");
|
|
114
|
+
if (input.proxySecretParents !== undefined)
|
|
115
|
+
refuse("proxySecretParents", "proxy secret parents are not supported");
|
|
116
|
+
if (input.providerOptions !== undefined && Object.keys(input.providerOptions).length > 0)
|
|
117
|
+
refuse("providerOptions", "no provider option is implemented");
|
|
118
|
+
const workspace = input.workspace;
|
|
119
|
+
if (workspace !== undefined) {
|
|
120
|
+
if (workspace.snapshot !== undefined)
|
|
121
|
+
refuse("workspace.snapshot", "workspace snapshots are not supported");
|
|
122
|
+
if (workspace.checkpoint !== undefined)
|
|
123
|
+
refuse("workspace.checkpoint", "workspace checkpoints are not supported");
|
|
124
|
+
if (workspace.cwd?.base === "host")
|
|
125
|
+
refuse("workspace.cwd", "a host cwd is refused; use base \"repository\"");
|
|
126
|
+
if (workspace.providerOptions !== undefined && Object.keys(workspace.providerOptions).length > 0)
|
|
127
|
+
refuse("workspace.providerOptions", "no workspace provider option is implemented");
|
|
128
|
+
if (workspace.environment !== undefined && workspace.image !== undefined)
|
|
129
|
+
refuse("workspace.image", "workspace cannot specify both environment and image");
|
|
130
|
+
if (workspace.environment !== undefined && !support.environment)
|
|
131
|
+
refuse("workspace.environment", "this vendor selects no template or snapshot");
|
|
132
|
+
if (workspace.image !== undefined && !support.image)
|
|
133
|
+
refuse("workspace.image", "this vendor selects no image");
|
|
134
|
+
}
|
|
135
|
+
if (input.resources !== undefined) {
|
|
136
|
+
if (!support.resources)
|
|
137
|
+
refuse("resources", "this vendor sets no resources at create");
|
|
138
|
+
if (input.resources.providerOptions !== undefined)
|
|
139
|
+
refuse("resources.providerOptions", "no resource provider option is implemented");
|
|
140
|
+
}
|
|
141
|
+
if (input.egress !== undefined && !support.egress.includes(input.egress.mode)) {
|
|
142
|
+
refuse("egress", `egress mode ${input.egress.mode} is not enforced by this vendor`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** The harnesses this module can declare. */
|
|
146
|
+
export function declarableHarnesses() {
|
|
147
|
+
return Object.keys(BOX_HARNESSES);
|
|
148
|
+
}
|
|
149
|
+
/** Whether the shared planner lowers this profile dimension for the harness. */
|
|
150
|
+
export function plannerLowers(harness, dimension) {
|
|
151
|
+
const probe = { hooks: { PostToolUse: [{ command: "true" }] } };
|
|
152
|
+
try {
|
|
153
|
+
const plan = materializeProfile(probe, harness);
|
|
154
|
+
return !plan.unsupported.some((entry) => entry.dimension === dimension);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { AgentEnvironmentEgressMode, AgentEnvironmentStatus, ExecRequest, ExecResult, ResourceRequest } from "@tangle-network/agent-interface/environment-provider";
|
|
2
|
+
/**
|
|
3
|
+
* What a cloud sandbox must expose so a CLI Bridge can run inside it.
|
|
4
|
+
*
|
|
5
|
+
* A vendor adapter is only this: create the sandbox from the box image, run
|
|
6
|
+
* commands in it, expose the Bridge port, report status, destroy it. Profile
|
|
7
|
+
* materialization, turns, events, usage, cancellation and refusals all belong
|
|
8
|
+
* to the Bridge and to {@link createBridgeSandboxProvider}.
|
|
9
|
+
*/
|
|
10
|
+
export interface BridgeSandbox {
|
|
11
|
+
/** The vendor's own sandbox id. It becomes `AgentEnvironment.id`. */
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly region?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Run a command to completion as the box user.
|
|
16
|
+
*
|
|
17
|
+
* A non-zero exit is a result, never a throw. `signal` and `timeoutMs` end
|
|
18
|
+
* the remote command itself, not only the client call.
|
|
19
|
+
*/
|
|
20
|
+
exec(command: string, options?: ExecRequest): Promise<ExecResult>;
|
|
21
|
+
/**
|
|
22
|
+
* Start a process that outlives this call, with `env` set on that process
|
|
23
|
+
* only. The Bridge runs this way, so its credentials never enter the sandbox
|
|
24
|
+
* default environment or any command line.
|
|
25
|
+
*/
|
|
26
|
+
spawn(command: string, options: {
|
|
27
|
+
cwd?: string;
|
|
28
|
+
env: Record<string, string>;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/** Exact bytes written by `write`; a relative path resolves against the workspace directory. */
|
|
31
|
+
read(path: string, options?: {
|
|
32
|
+
signal?: AbortSignal;
|
|
33
|
+
}): Promise<string>;
|
|
34
|
+
/** Creates parent directories. */
|
|
35
|
+
write(path: string, content: string, options?: {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
/** The URL that reaches `port` inside the sandbox, plus any headers the vendor's gateway requires. */
|
|
39
|
+
endpoint(port: number, options?: {
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
}): Promise<{
|
|
42
|
+
url: string;
|
|
43
|
+
headers?: Record<string, string>;
|
|
44
|
+
}>;
|
|
45
|
+
/** The vendor's current lifecycle state; `stopped` or `expired` after destroy or expiry. */
|
|
46
|
+
status(options?: {
|
|
47
|
+
signal?: AbortSignal;
|
|
48
|
+
}): Promise<AgentEnvironmentStatus>;
|
|
49
|
+
/** Delete the sandbox. Resolves when it is already gone. */
|
|
50
|
+
destroy(options?: {
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export interface BridgeSandboxCreateRequest {
|
|
55
|
+
name?: string;
|
|
56
|
+
/** Tags the vendor stores with the sandbox, string values only. Always carries the run tag. */
|
|
57
|
+
metadata: Record<string, string>;
|
|
58
|
+
/** Caller environment for the sandbox's default exec environment. */
|
|
59
|
+
env: Record<string, string>;
|
|
60
|
+
/** Finite lifetime the vendor enforces; longer than any turn the caller may run. */
|
|
61
|
+
lifetimeMs: number;
|
|
62
|
+
/** Vendor template, snapshot or image the caller chose; absent selects the published box image. */
|
|
63
|
+
environment?: string;
|
|
64
|
+
image?: string;
|
|
65
|
+
resources?: ResourceRequest;
|
|
66
|
+
egress?: {
|
|
67
|
+
mode: AgentEnvironmentEgressMode;
|
|
68
|
+
allowDomains: readonly string[];
|
|
69
|
+
};
|
|
70
|
+
/** Aborting must cancel the vendor create and destroy a sandbox that arrives late. */
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
}
|
|
73
|
+
export interface BridgeSandboxSummary {
|
|
74
|
+
id: string;
|
|
75
|
+
status: AgentEnvironmentStatus;
|
|
76
|
+
name?: string;
|
|
77
|
+
metadata: Record<string, string>;
|
|
78
|
+
}
|
|
79
|
+
/** Create-input fields a vendor honors beyond the always-honored ones. Everything else is refused. */
|
|
80
|
+
export interface BridgeVendorSupport {
|
|
81
|
+
/** `workspace.environment` selects a vendor template or snapshot. */
|
|
82
|
+
environment: boolean;
|
|
83
|
+
/** `workspace.image` selects a vendor image. */
|
|
84
|
+
image: boolean;
|
|
85
|
+
resources: boolean;
|
|
86
|
+
/** Egress modes the vendor enforces; absent modes are refused. */
|
|
87
|
+
egress: readonly AgentEnvironmentEgressMode[];
|
|
88
|
+
}
|
|
89
|
+
export interface BridgeSandboxVendor {
|
|
90
|
+
/** Provider name reported on every environment, for example `e2b`. */
|
|
91
|
+
readonly name: string;
|
|
92
|
+
readonly support: BridgeVendorSupport;
|
|
93
|
+
create(request: BridgeSandboxCreateRequest): Promise<BridgeSandbox>;
|
|
94
|
+
/** `null` only when the vendor definitively reports no such sandbox; transport failures throw. */
|
|
95
|
+
connect(id: string, options?: {
|
|
96
|
+
signal?: AbortSignal;
|
|
97
|
+
}): Promise<BridgeSandbox | null>;
|
|
98
|
+
/** Sandboxes whose tags contain every entry of `metadata`, with their true status. */
|
|
99
|
+
list(query: {
|
|
100
|
+
name?: string;
|
|
101
|
+
metadata?: Record<string, string>;
|
|
102
|
+
}, options?: {
|
|
103
|
+
signal?: AbortSignal;
|
|
104
|
+
}): Promise<BridgeSandboxSummary[]>;
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/wire.d.ts
CHANGED
|
@@ -27,11 +27,14 @@ export declare function toolCallsFromDelta(value: unknown): Array<{
|
|
|
27
27
|
}>;
|
|
28
28
|
export declare function safeJson(value: string): Record<string, unknown> | null;
|
|
29
29
|
export declare function usageFromOpenAi(value: unknown): TokenUsage | undefined;
|
|
30
|
+
export declare function providerUsageFromOpenAi(value: unknown): Record<string, unknown> | undefined;
|
|
30
31
|
export declare function modelRequestsFromOpenAi(value: unknown): number | undefined;
|
|
31
|
-
/**
|
|
32
|
+
/** Compatibility model and explicit provider observations have different authority. */
|
|
32
33
|
export declare function responseIdentityFromOpenAi(value: unknown): {
|
|
33
34
|
model?: string;
|
|
34
35
|
system_fingerprint?: string;
|
|
36
|
+
provider_models?: string[];
|
|
37
|
+
provider_model?: string;
|
|
35
38
|
} | undefined;
|
|
36
39
|
export declare function modelRequestCount(value: unknown): number | undefined;
|
|
37
40
|
export declare function cliBridgeRunId(environmentId: string, turn: AgentTurnInput, turnId: string): string;
|
package/dist/wire.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { AgentExactRunControlRefSchema, RequestedInteractionsSchema, WorkspaceRequestSchema, workspaceCwdPathForBase, } from "@tangle-network/agent-interface";
|
|
2
|
+
import { AgentExactRunControlRefSchema, CONTRACT_MAX_ARRAY_LENGTH, RequestedInteractionsSchema, WorkspaceRequestSchema, workspaceCwdPathForBase, } from "@tangle-network/agent-interface";
|
|
3
3
|
export function toChatCompletionsBody(options, environmentInput, turn, coordinates) {
|
|
4
4
|
const profile = inlineProfile(environmentInput.profile);
|
|
5
5
|
const execution = executionFromInput(options, environmentInput);
|
|
@@ -257,47 +257,61 @@ export function safeJson(value) {
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
export function usageFromOpenAi(value) {
|
|
260
|
-
|
|
260
|
+
const record = providerUsageFromOpenAi(value);
|
|
261
|
+
if (!record)
|
|
261
262
|
return undefined;
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
nonNegativeSafeInteger(record.input_tokens);
|
|
265
|
-
const outputTokens = nonNegativeSafeInteger(record.completion_tokens) ??
|
|
266
|
-
nonNegativeSafeInteger(record.output_tokens);
|
|
263
|
+
const inputTokens = nonNegativeSafeInteger(record.prompt_tokens) ?? nonNegativeSafeInteger(record.input_tokens);
|
|
264
|
+
const outputTokens = nonNegativeSafeInteger(record.completion_tokens) ?? nonNegativeSafeInteger(record.output_tokens);
|
|
267
265
|
if (inputTokens === undefined || outputTokens === undefined)
|
|
268
266
|
return undefined;
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
267
|
+
const usage = { inputTokens, outputTokens };
|
|
268
|
+
for (const [key, wireKey] of [
|
|
269
|
+
["totalTokens", "total_tokens"], ["cacheReadInputTokens", "cache_read_input_tokens"],
|
|
270
|
+
["cacheCreationInputTokens", "cache_write_input_tokens"], ["reasoningTokens", "reasoning_tokens"],
|
|
271
|
+
]) {
|
|
272
|
+
const count = nonNegativeSafeInteger(record[wireKey]) ??
|
|
273
|
+
(key === "cacheCreationInputTokens" ? nonNegativeSafeInteger(record.cache_creation_input_tokens) : undefined);
|
|
274
|
+
if (count !== undefined)
|
|
275
|
+
usage[key] = count;
|
|
276
|
+
}
|
|
273
277
|
const cost = finiteNonNegativeNumber(record.cost);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
...(cost !== undefined ? { cost } : {}),
|
|
282
|
-
};
|
|
278
|
+
if (cost !== undefined)
|
|
279
|
+
usage.cost = cost;
|
|
280
|
+
return usage;
|
|
281
|
+
}
|
|
282
|
+
export function providerUsageFromOpenAi(value) {
|
|
283
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
284
|
+
? value : undefined;
|
|
283
285
|
}
|
|
284
286
|
export function modelRequestsFromOpenAi(value) {
|
|
285
287
|
if (!value || typeof value !== "object")
|
|
286
288
|
return undefined;
|
|
287
289
|
return modelRequestCount(value.model_requests);
|
|
288
290
|
}
|
|
289
|
-
/**
|
|
291
|
+
/** Compatibility model and explicit provider observations have different authority. */
|
|
290
292
|
export function responseIdentityFromOpenAi(value) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const record = value;
|
|
294
|
-
const model = nonEmptyString(record.model);
|
|
295
|
-
const systemFingerprint = nonEmptyString(record.system_fingerprint);
|
|
296
|
-
if (model === undefined && systemFingerprint === undefined)
|
|
293
|
+
const record = providerUsageFromOpenAi(value);
|
|
294
|
+
if (!record)
|
|
297
295
|
return undefined;
|
|
296
|
+
if (record.provider_model !== undefined && nonEmptyString(record.provider_model) === undefined) {
|
|
297
|
+
throw new Error("cli-bridge returned an invalid provider model observation");
|
|
298
|
+
}
|
|
299
|
+
const models = record.provider_models;
|
|
300
|
+
if (models !== undefined && (!Array.isArray(models) || models.length > CONTRACT_MAX_ARRAY_LENGTH ||
|
|
301
|
+
models.some(model => nonEmptyString(model) === undefined))) {
|
|
302
|
+
throw new Error("cli-bridge returned invalid provider model observations");
|
|
303
|
+
}
|
|
304
|
+
const observations = [...new Set([
|
|
305
|
+
...(models ?? []),
|
|
306
|
+
...(nonEmptyString(record.provider_model) ? [record.provider_model] : []),
|
|
307
|
+
])];
|
|
308
|
+
if (observations.length > CONTRACT_MAX_ARRAY_LENGTH)
|
|
309
|
+
throw new Error("cli-bridge provider model observations exceed contract bounds");
|
|
298
310
|
return {
|
|
299
|
-
...(model
|
|
300
|
-
...(
|
|
311
|
+
...(nonEmptyString(record.model) ? { model: record.model } : {}),
|
|
312
|
+
...(nonEmptyString(record.system_fingerprint) ? { system_fingerprint: record.system_fingerprint } : {}),
|
|
313
|
+
...(observations.length ? { provider_models: observations } : {}),
|
|
314
|
+
...(observations.length === 1 ? { provider_model: observations[0] } : {}),
|
|
301
315
|
};
|
|
302
316
|
}
|
|
303
317
|
export function modelRequestCount(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-cli-bridge",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for cli-bridge retained sessions and one-shot turns",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"import": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./sandbox": {
|
|
19
|
+
"import": "./dist/sandbox/index.js",
|
|
20
|
+
"types": "./dist/sandbox/index.d.ts",
|
|
21
|
+
"default": "./dist/sandbox/index.js"
|
|
17
22
|
}
|
|
18
23
|
},
|
|
19
24
|
"repository": {
|
|
@@ -60,15 +65,29 @@
|
|
|
60
65
|
"dist/transport.js",
|
|
61
66
|
"dist/wire.d.ts",
|
|
62
67
|
"dist/wire.js",
|
|
68
|
+
"dist/sandbox/index.d.ts",
|
|
69
|
+
"dist/sandbox/index.js",
|
|
70
|
+
"dist/sandbox/box.d.ts",
|
|
71
|
+
"dist/sandbox/box.js",
|
|
72
|
+
"dist/sandbox/image.d.ts",
|
|
73
|
+
"dist/sandbox/image.js",
|
|
74
|
+
"dist/sandbox/provider.d.ts",
|
|
75
|
+
"dist/sandbox/provider.js",
|
|
76
|
+
"dist/sandbox/refusal.d.ts",
|
|
77
|
+
"dist/sandbox/refusal.js",
|
|
78
|
+
"dist/sandbox/vendor.d.ts",
|
|
79
|
+
"dist/sandbox/vendor.js",
|
|
63
80
|
"README.md",
|
|
64
81
|
"LICENSE"
|
|
65
82
|
],
|
|
66
83
|
"dependencies": {
|
|
67
|
-
"@tangle-network/agent-interface": "^5.10.
|
|
68
|
-
"undici": "8.12.3"
|
|
84
|
+
"@tangle-network/agent-interface": "^5.10.12",
|
|
85
|
+
"undici": "8.12.3",
|
|
86
|
+
"@tangle-network/agent-profile-materialize": "1.1.4",
|
|
87
|
+
"@tangle-network/cli-agent-registry": "^0.14.0"
|
|
69
88
|
},
|
|
70
89
|
"devDependencies": {
|
|
71
|
-
"@tangle-network/agent-provider-testkit": "0.12.
|
|
90
|
+
"@tangle-network/agent-provider-testkit": "0.12.2",
|
|
72
91
|
"@types/node": "26.6.5",
|
|
73
92
|
"typescript": "6.0.3",
|
|
74
93
|
"vitest": "5.0.5"
|
|
@@ -78,6 +97,6 @@
|
|
|
78
97
|
"check-types": "tsc --noEmit",
|
|
79
98
|
"clean": "rm -rf dist",
|
|
80
99
|
"test": "vitest run src",
|
|
81
|
-
"test:integration": "test -n \"$CLI_BRIDGE_INTEGRATION_ROOT\" || { echo 'CLI_BRIDGE_INTEGRATION_ROOT is required; see README' >&2; exit 1; }; pnpm run build && vitest run tests
|
|
100
|
+
"test:integration": "test -n \"$CLI_BRIDGE_INTEGRATION_ROOT\" || { echo 'CLI_BRIDGE_INTEGRATION_ROOT is required; see README' >&2; exit 1; }; pnpm run build && vitest run tests"
|
|
82
101
|
}
|
|
83
102
|
}
|