@tangle-network/agent-provider-cli-bridge 0.5.0 → 0.6.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 +8 -0
- package/dist/index.d.ts +5 -27
- package/dist/index.js +38 -998
- package/dist/provider-options.d.ts +26 -0
- package/dist/provider-options.js +10 -0
- package/dist/retained-control.d.ts +11 -0
- package/dist/retained-control.js +170 -0
- package/dist/retained-environment.d.ts +11 -0
- package/dist/retained-environment.js +189 -0
- package/dist/retained-event-cursor.d.ts +18 -0
- package/dist/retained-event-cursor.js +68 -0
- package/dist/retained-execution.d.ts +12 -0
- package/dist/retained-execution.js +293 -0
- package/dist/retained-run-state.d.ts +38 -0
- package/dist/retained-run-state.js +97 -0
- package/dist/retained-stream.d.ts +8 -0
- package/dist/retained-stream.js +251 -0
- package/dist/transport.d.ts +23 -0
- package/dist/transport.js +32 -0
- package/dist/wire.d.ts +22 -0
- package/dist/wire.js +226 -0
- package/package.json +19 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CliBridgeProviderOptions } from "./provider-options.js";
|
|
2
|
+
export interface CliBridgeTransport {
|
|
3
|
+
fetch(input: string, init: CliBridgeRequest): Promise<CliBridgeResponse>;
|
|
4
|
+
close(): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export interface CliBridgeRequest {
|
|
7
|
+
method: "GET" | "POST";
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
body?: string;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface CliBridgeResponse {
|
|
13
|
+
readonly ok: boolean;
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly body: AsyncIterable<Uint8Array> | null;
|
|
16
|
+
readonly headers: {
|
|
17
|
+
get(name: string): string | null;
|
|
18
|
+
};
|
|
19
|
+
text(): Promise<string>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createCliBridgeTransport(options: CliBridgeProviderOptions): CliBridgeTransport;
|
|
22
|
+
export declare function requestHeaders(options: CliBridgeProviderOptions): Record<string, string>;
|
|
23
|
+
export declare function trimSlash(value: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
2
|
+
export function createCliBridgeTransport(options) {
|
|
3
|
+
if (options.fetch) {
|
|
4
|
+
const fetch = options.fetch;
|
|
5
|
+
return {
|
|
6
|
+
fetch: (input, init) => fetch(input, init),
|
|
7
|
+
close: async () => { },
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const dispatcher = new Agent({
|
|
11
|
+
headersTimeout: options.headersTimeoutMs ?? 0,
|
|
12
|
+
bodyTimeout: options.bodyTimeoutMs ?? 0,
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
fetch: (input, init) => undiciFetch(input, {
|
|
16
|
+
...init,
|
|
17
|
+
dispatcher,
|
|
18
|
+
}),
|
|
19
|
+
close: async () => {
|
|
20
|
+
await dispatcher.close();
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function requestHeaders(options) {
|
|
25
|
+
return {
|
|
26
|
+
"content-type": "application/json",
|
|
27
|
+
...(options.bearerToken ? { authorization: `Bearer ${options.bearerToken}` } : {}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function trimSlash(value) {
|
|
31
|
+
return value.replace(/\/+$/, "");
|
|
32
|
+
}
|
package/dist/wire.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TokenUsage } from "@tangle-network/agent-interface";
|
|
2
|
+
import type { AgentTurnInput, CreateAgentEnvironmentInput } from "@tangle-network/agent-interface/environment-provider";
|
|
3
|
+
import type { CliBridgeProviderOptions } from "./provider-options.js";
|
|
4
|
+
import type { CliBridgeRunSnapshot } from "./retained-run-state.js";
|
|
5
|
+
export declare function toChatCompletionsBody(options: CliBridgeProviderOptions, environmentInput: CreateAgentEnvironmentInput, turn: AgentTurnInput, runId: string): Record<string, unknown>;
|
|
6
|
+
export interface CliBridgeSseFrame {
|
|
7
|
+
readonly data: string;
|
|
8
|
+
readonly id?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseSse(body: AsyncIterable<Uint8Array>): AsyncIterable<CliBridgeSseFrame>;
|
|
11
|
+
export declare function toolCallsFromDelta(value: unknown): Array<{
|
|
12
|
+
id?: string;
|
|
13
|
+
index: number;
|
|
14
|
+
name?: string;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function safeJson(value: string): Record<string, unknown> | null;
|
|
17
|
+
export declare function usageFromOpenAi(value: unknown): TokenUsage | undefined;
|
|
18
|
+
export declare function modelRequestsFromOpenAi(value: unknown): number | undefined;
|
|
19
|
+
export declare function modelRequestCount(value: unknown): number | undefined;
|
|
20
|
+
export declare function cliBridgeRunId(environmentId: string, turn: AgentTurnInput, turnId: string): string;
|
|
21
|
+
export declare function runSnapshot(value: string): CliBridgeRunSnapshot;
|
|
22
|
+
export declare function cancelSnapshot(value: string): CliBridgeRunSnapshot;
|
package/dist/wire.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { AgentExactRunControlRefSchema } from "@tangle-network/agent-interface";
|
|
3
|
+
export function toChatCompletionsBody(options, environmentInput, turn, runId) {
|
|
4
|
+
const profile = inlineProfile(environmentInput.profile);
|
|
5
|
+
const execution = executionFromInput(options, environmentInput);
|
|
6
|
+
return {
|
|
7
|
+
model: resolveBridgeModel(options, environmentInput, turn, profile),
|
|
8
|
+
messages: messagesFromTurn(turn),
|
|
9
|
+
stream: true,
|
|
10
|
+
...(turn.sessionId ? { session_id: turn.sessionId } : {}),
|
|
11
|
+
run_id: runId,
|
|
12
|
+
...(options.defaultMode ? { mode: options.defaultMode } : {}),
|
|
13
|
+
...(profile ? { agent_profile: profile } : {}),
|
|
14
|
+
...(profile?.model?.reasoningEffort
|
|
15
|
+
? { effort: profile.model.reasoningEffort }
|
|
16
|
+
: {}),
|
|
17
|
+
...(environmentInput.env ? { env: environmentInput.env } : {}),
|
|
18
|
+
...(environmentInput.workspace?.cwd ? { cwd: environmentInput.workspace.cwd } : {}),
|
|
19
|
+
...(execution ? { execution } : {}),
|
|
20
|
+
metadata: {
|
|
21
|
+
...(environmentInput.metadata ?? {}),
|
|
22
|
+
...(turn.context ?? {}),
|
|
23
|
+
...(turn.providerOptions ?? {}),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function resolveBridgeModel(options, environmentInput, turn, profile) {
|
|
28
|
+
const harness = environmentInput.backend ?? profile?.harness;
|
|
29
|
+
const model = turn.model ?? options.defaultModel ?? profile?.model?.default;
|
|
30
|
+
const provider = profile?.model?.provider;
|
|
31
|
+
if (!harness) {
|
|
32
|
+
if (model)
|
|
33
|
+
return model;
|
|
34
|
+
throw new Error("createCliBridgeProvider requires an explicit bridge model or a profile/backend harness");
|
|
35
|
+
}
|
|
36
|
+
if (!model || model === harness)
|
|
37
|
+
return harness;
|
|
38
|
+
if (model.startsWith(`${harness}/`))
|
|
39
|
+
return model;
|
|
40
|
+
if (model.includes("/"))
|
|
41
|
+
return `${harness}/${model}`;
|
|
42
|
+
if (provider)
|
|
43
|
+
return `${harness}/${provider}/${model}`;
|
|
44
|
+
return `${harness}/${model}`;
|
|
45
|
+
}
|
|
46
|
+
/** Keep task messages separate from the profile's harness-owned prompt controls. */
|
|
47
|
+
function messagesFromTurn(turn) {
|
|
48
|
+
return [{ role: "user", content: contentFromTurn(turn) }];
|
|
49
|
+
}
|
|
50
|
+
function contentFromTurn(turn) {
|
|
51
|
+
if (turn.parts)
|
|
52
|
+
return turn.parts;
|
|
53
|
+
return turn.prompt ?? "";
|
|
54
|
+
}
|
|
55
|
+
function inlineProfile(profile) {
|
|
56
|
+
return typeof profile === "string" ? undefined : profile;
|
|
57
|
+
}
|
|
58
|
+
function executionFromInput(options, input) {
|
|
59
|
+
if (options.defaultExecution)
|
|
60
|
+
return options.defaultExecution;
|
|
61
|
+
if (!input.workspace?.repoUrl)
|
|
62
|
+
return undefined;
|
|
63
|
+
return {
|
|
64
|
+
kind: "sandbox",
|
|
65
|
+
repoUrl: input.workspace.repoUrl,
|
|
66
|
+
...(input.workspace.gitRef ? { gitRef: input.workspace.gitRef } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export async function* parseSse(body) {
|
|
70
|
+
const decoder = new TextDecoder();
|
|
71
|
+
let buffer = "";
|
|
72
|
+
for await (const value of body) {
|
|
73
|
+
buffer += decoder.decode(value, { stream: true });
|
|
74
|
+
let boundary = findFrameBoundary(buffer);
|
|
75
|
+
while (boundary) {
|
|
76
|
+
const frame = buffer.slice(0, boundary.index);
|
|
77
|
+
buffer = buffer.slice(boundary.index + boundary.length);
|
|
78
|
+
const data = dataFromFrame(frame);
|
|
79
|
+
if (data)
|
|
80
|
+
yield data;
|
|
81
|
+
boundary = findFrameBoundary(buffer);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
buffer += decoder.decode();
|
|
85
|
+
if (buffer) {
|
|
86
|
+
const data = dataFromFrame(buffer);
|
|
87
|
+
if (data)
|
|
88
|
+
yield data;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function findFrameBoundary(value) {
|
|
92
|
+
const lf = value.indexOf("\n\n");
|
|
93
|
+
const crlf = value.indexOf("\r\n\r\n");
|
|
94
|
+
if (lf === -1 && crlf === -1)
|
|
95
|
+
return undefined;
|
|
96
|
+
if (crlf !== -1 && (lf === -1 || crlf < lf))
|
|
97
|
+
return { index: crlf, length: 4 };
|
|
98
|
+
return { index: lf, length: 2 };
|
|
99
|
+
}
|
|
100
|
+
function dataFromFrame(frame) {
|
|
101
|
+
const lines = frame.split(/\r?\n/);
|
|
102
|
+
const data = lines
|
|
103
|
+
.filter((line) => line.startsWith("data:"))
|
|
104
|
+
.map((line) => line.slice("data:".length).trimStart())
|
|
105
|
+
.join("\n");
|
|
106
|
+
if (!data)
|
|
107
|
+
return undefined;
|
|
108
|
+
const id = lines.find((line) => line.startsWith("id:"))?.slice("id:".length).trim();
|
|
109
|
+
return { data, ...(id ? { id } : {}) };
|
|
110
|
+
}
|
|
111
|
+
export function toolCallsFromDelta(value) {
|
|
112
|
+
if (!value || typeof value !== "object")
|
|
113
|
+
return [];
|
|
114
|
+
const calls = value.tool_calls;
|
|
115
|
+
if (!Array.isArray(calls))
|
|
116
|
+
return [];
|
|
117
|
+
return calls.flatMap((call, position) => {
|
|
118
|
+
if (!call || typeof call !== "object")
|
|
119
|
+
return [];
|
|
120
|
+
const record = call;
|
|
121
|
+
const fn = record.function;
|
|
122
|
+
const name = fn && typeof fn === "object" && typeof fn.name === "string"
|
|
123
|
+
? fn.name
|
|
124
|
+
: undefined;
|
|
125
|
+
const index = nonNegativeSafeInteger(record.index) ?? position;
|
|
126
|
+
const id = typeof record.id === "string" ? record.id : undefined;
|
|
127
|
+
return [{ ...(id ? { id } : {}), index, ...(name ? { name } : {}) }];
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export function safeJson(value) {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(value);
|
|
133
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function usageFromOpenAi(value) {
|
|
140
|
+
if (!value || typeof value !== "object")
|
|
141
|
+
return undefined;
|
|
142
|
+
const record = value;
|
|
143
|
+
const inputTokens = nonNegativeSafeInteger(record.prompt_tokens) ??
|
|
144
|
+
nonNegativeSafeInteger(record.input_tokens);
|
|
145
|
+
const outputTokens = nonNegativeSafeInteger(record.completion_tokens) ??
|
|
146
|
+
nonNegativeSafeInteger(record.output_tokens);
|
|
147
|
+
if (inputTokens === undefined || outputTokens === undefined)
|
|
148
|
+
return undefined;
|
|
149
|
+
const totalTokens = nonNegativeSafeInteger(record.total_tokens);
|
|
150
|
+
const cacheReadInputTokens = nonNegativeSafeInteger(record.cache_read_input_tokens);
|
|
151
|
+
const cacheCreationInputTokens = nonNegativeSafeInteger(record.cache_creation_input_tokens);
|
|
152
|
+
const reasoningTokens = nonNegativeSafeInteger(record.reasoning_tokens);
|
|
153
|
+
const cost = finiteNonNegativeNumber(record.cost);
|
|
154
|
+
return {
|
|
155
|
+
inputTokens,
|
|
156
|
+
outputTokens,
|
|
157
|
+
...(totalTokens !== undefined ? { totalTokens } : {}),
|
|
158
|
+
...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
|
|
159
|
+
...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
|
|
160
|
+
...(reasoningTokens !== undefined ? { reasoningTokens } : {}),
|
|
161
|
+
...(cost !== undefined ? { cost } : {}),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function modelRequestsFromOpenAi(value) {
|
|
165
|
+
if (!value || typeof value !== "object")
|
|
166
|
+
return undefined;
|
|
167
|
+
return modelRequestCount(value.model_requests);
|
|
168
|
+
}
|
|
169
|
+
export function modelRequestCount(value) {
|
|
170
|
+
return nonNegativeSafeInteger(value);
|
|
171
|
+
}
|
|
172
|
+
function nonNegativeSafeInteger(value) {
|
|
173
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
174
|
+
? value
|
|
175
|
+
: undefined;
|
|
176
|
+
}
|
|
177
|
+
function finiteNonNegativeNumber(value) {
|
|
178
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
179
|
+
? value
|
|
180
|
+
: undefined;
|
|
181
|
+
}
|
|
182
|
+
export function cliBridgeRunId(environmentId, turn, turnId) {
|
|
183
|
+
if (turn.executionId &&
|
|
184
|
+
turn.executionId.length <= 128 &&
|
|
185
|
+
/^[A-Za-z0-9][A-Za-z0-9._:-]*$/u.test(turn.executionId)) {
|
|
186
|
+
return turn.executionId;
|
|
187
|
+
}
|
|
188
|
+
const digest = createHash("sha256")
|
|
189
|
+
.update(environmentId)
|
|
190
|
+
.update("\0")
|
|
191
|
+
.update(turn.sessionId ?? "")
|
|
192
|
+
.update("\0")
|
|
193
|
+
.update(turn.executionId ?? turnId)
|
|
194
|
+
.digest("hex");
|
|
195
|
+
return `agent-${digest}`;
|
|
196
|
+
}
|
|
197
|
+
export function runSnapshot(value) {
|
|
198
|
+
const parsed = safeJson(value);
|
|
199
|
+
if (!parsed)
|
|
200
|
+
throw new Error("cli-bridge run status returned invalid JSON");
|
|
201
|
+
const id = parsed.id;
|
|
202
|
+
const requestDigest = parsed.requestDigest === undefined
|
|
203
|
+
? undefined
|
|
204
|
+
: AgentExactRunControlRefSchema.shape.requestDigest.safeParse(parsed.requestDigest);
|
|
205
|
+
const status = parsed.status;
|
|
206
|
+
const terminal = parsed.terminal;
|
|
207
|
+
if (typeof id !== "string" ||
|
|
208
|
+
(requestDigest !== undefined && !requestDigest.success) ||
|
|
209
|
+
!["running", "done", "error", "cancelled"].includes(String(status)) ||
|
|
210
|
+
typeof terminal !== "boolean") {
|
|
211
|
+
throw new Error("cli-bridge run status returned an invalid snapshot");
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
id,
|
|
215
|
+
...(requestDigest?.success ? { requestDigest: requestDigest.data } : {}),
|
|
216
|
+
status: status,
|
|
217
|
+
terminal,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export function cancelSnapshot(value) {
|
|
221
|
+
const parsed = safeJson(value);
|
|
222
|
+
if (!parsed || !parsed.run || typeof parsed.run !== "object") {
|
|
223
|
+
throw new Error("cli-bridge cancel returned an invalid snapshot");
|
|
224
|
+
}
|
|
225
|
+
return runSnapshot(JSON.stringify(parsed.run));
|
|
226
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-provider-cli-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AgentEnvironmentProvider adapter for cli-bridge /v1/chat/completions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,24 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/index.d.ts",
|
|
27
27
|
"dist/index.js",
|
|
28
|
+
"dist/provider-options.d.ts",
|
|
29
|
+
"dist/provider-options.js",
|
|
30
|
+
"dist/retained-control.d.ts",
|
|
31
|
+
"dist/retained-control.js",
|
|
32
|
+
"dist/retained-environment.d.ts",
|
|
33
|
+
"dist/retained-environment.js",
|
|
34
|
+
"dist/retained-event-cursor.d.ts",
|
|
35
|
+
"dist/retained-event-cursor.js",
|
|
36
|
+
"dist/retained-execution.d.ts",
|
|
37
|
+
"dist/retained-execution.js",
|
|
38
|
+
"dist/retained-run-state.d.ts",
|
|
39
|
+
"dist/retained-run-state.js",
|
|
40
|
+
"dist/retained-stream.d.ts",
|
|
41
|
+
"dist/retained-stream.js",
|
|
42
|
+
"dist/transport.d.ts",
|
|
43
|
+
"dist/transport.js",
|
|
44
|
+
"dist/wire.d.ts",
|
|
45
|
+
"dist/wire.js",
|
|
28
46
|
"README.md",
|
|
29
47
|
"LICENSE"
|
|
30
48
|
],
|