@hue-run/sdk 0.9.0 → 0.11.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/CLI.md +240 -49
- package/ENVIRONMENTS.md +48 -9
- package/EVALUATIONS.md +41 -21
- package/README.md +70 -30
- package/dist/cli/eval-direct.d.ts +12 -1
- package/dist/cli/eval-direct.js +145 -33
- package/dist/cli/eval.d.ts +28 -1
- package/dist/cli/eval.js +504 -81
- package/dist/cli/listen.d.ts +76 -0
- package/dist/cli/listen.js +816 -0
- package/dist/cli/mcp.d.ts +65 -7
- package/dist/cli/mcp.js +271 -73
- package/dist/client.d.ts +7 -4
- package/dist/client.js +36 -10
- package/dist/evals/checkpoint.d.ts +15 -1
- package/dist/evals/checkpoint.js +34 -3
- package/dist/evals/client.d.ts +24 -4
- package/dist/evals/client.js +92 -38
- package/dist/evals/environment-target.d.ts +18 -3
- package/dist/evals/environment-target.js +22 -10
- package/dist/evals/exit-cleanup.d.ts +4 -0
- package/dist/evals/exit-cleanup.js +26 -0
- package/dist/evals/files.d.ts +69 -3
- package/dist/evals/files.js +255 -42
- package/dist/evals/json.js +116 -9
- package/dist/evals/local-worker.d.ts +21 -3
- package/dist/evals/local-worker.js +14 -0
- package/dist/evals/runner.d.ts +45 -0
- package/dist/evals/runner.js +199 -21
- package/dist/evals/scenarios.d.ts +7 -5
- package/dist/evals/scenarios.js +71 -14
- package/dist/evals/scorer-publication.js +58 -3
- package/dist/evals/simulation.d.ts +17 -4
- package/dist/evals/simulation.js +8 -0
- package/dist/evals/types.d.ts +48 -0
- package/dist/evals/verdicts.d.ts +26 -2
- package/dist/evals/verdicts.js +72 -9
- package/dist/evals.d.ts +4 -3
- package/dist/evals.js +2 -2
- package/dist/inline-files.d.ts +4 -0
- package/dist/inline-files.js +48 -17
- package/dist/provider-tools.d.ts +12 -1
- package/dist/provider-tools.js +192 -106
- package/dist/safety.d.ts +7 -1
- package/dist/safety.js +9 -7
- package/dist/setup/cli.js +31 -8
- package/dist/setup/peers.d.ts +13 -0
- package/dist/setup/peers.js +44 -0
- package/dist/snapshot.d.ts +2 -2
- package/dist/snapshot.js +47 -12
- package/dist/tool-definitions.d.ts +18 -0
- package/dist/tool-definitions.js +232 -0
- package/dist/transport.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/provider-tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MAX_CONTENT_BYTES } from "./config.js";
|
|
2
|
+
import { scrubCredentialText } from "./tool-definitions.js";
|
|
2
3
|
const MAX_PROVIDER_ITEMS = 128;
|
|
3
4
|
const MAX_PROVIDER_DEFINITIONS = 512;
|
|
4
5
|
const MAX_PROVIDER_SERVERS = 512;
|
|
@@ -19,13 +20,28 @@ function isProviderToolItem(provider, value) {
|
|
|
19
20
|
return value.type === "mcp_tool_use" || value.type === "server_tool_use";
|
|
20
21
|
}
|
|
21
22
|
function countProviderToolItems(provider, items, start = 0) {
|
|
23
|
+
let keys;
|
|
24
|
+
try {
|
|
25
|
+
keys = Object.keys(items);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// A tail that cannot be listed still holds at least one item nothing could read.
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
22
31
|
let count = 0;
|
|
23
|
-
for (const key of
|
|
32
|
+
for (const key of keys) {
|
|
24
33
|
const index = Number(key);
|
|
25
34
|
if (!Number.isInteger(index) || index < start || index >= items.length || String(index) !== key)
|
|
26
35
|
continue;
|
|
27
|
-
|
|
36
|
+
try {
|
|
37
|
+
if (isProviderToolItem(provider, items[index]))
|
|
38
|
+
count++;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// A tail item whose `type` cannot be read counts as skipped; the bounded prefix is still
|
|
42
|
+
// recorded, as the Python SDK does.
|
|
28
43
|
count++;
|
|
44
|
+
}
|
|
29
45
|
}
|
|
30
46
|
return count;
|
|
31
47
|
}
|
|
@@ -41,6 +57,42 @@ function text(value) {
|
|
|
41
57
|
function errorCode(value) {
|
|
42
58
|
return typeof value === "string" && /^[a-z0-9_]{1,64}$/.test(value) ? value : "error";
|
|
43
59
|
}
|
|
60
|
+
/** The most of a provider's error text scrubbed, and the most exported, in code points. */
|
|
61
|
+
const MAX_ERROR_SCAN = 16_384;
|
|
62
|
+
const MAX_ERROR_TEXT = 1_024;
|
|
63
|
+
/** The first `count` code points of `text`. */
|
|
64
|
+
function codePoints(text, count) {
|
|
65
|
+
let kept = "";
|
|
66
|
+
let seen = 0;
|
|
67
|
+
for (const character of text) {
|
|
68
|
+
if (seen++ === count)
|
|
69
|
+
return { text: kept, cut: true };
|
|
70
|
+
kept += character;
|
|
71
|
+
}
|
|
72
|
+
return { text: kept, cut: false };
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A provider's error text as a failed call exports it under content capture: credentials
|
|
76
|
+
* scrubbed first, then at most 1,024 code points with `…` marking a cut, lone surrogates
|
|
77
|
+
* replaced and NUL removed. The Python SDK produces the same text.
|
|
78
|
+
*/
|
|
79
|
+
export function providerErrorDescription(value) {
|
|
80
|
+
const scanned = codePoints(value.toWellFormed().replaceAll("\u0000", ""), MAX_ERROR_SCAN);
|
|
81
|
+
const bounded = codePoints(scrubCredentialText(scanned.text), MAX_ERROR_TEXT);
|
|
82
|
+
return bounded.cut || scanned.cut ? `${bounded.text}…` : bounded.text;
|
|
83
|
+
}
|
|
84
|
+
/** An OpenAI MCP call's `error`: its text, or an error object's string `message`. */
|
|
85
|
+
function errorText(value) {
|
|
86
|
+
let message;
|
|
87
|
+
try {
|
|
88
|
+
message = isItem(value) ? value.message : value;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// An error whose message cannot be read still fails its call; only the text is lost.
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return typeof message === "string" && message.trim() !== "" ? message : undefined;
|
|
95
|
+
}
|
|
44
96
|
/** MCP arguments arrive as a JSON string; record the structure when it parses, else the text. */
|
|
45
97
|
function jsonArguments(value) {
|
|
46
98
|
if (typeof value !== "string")
|
|
@@ -59,88 +111,105 @@ function openaiCalls(items, activity) {
|
|
|
59
111
|
const count = Math.min(items.length, MAX_PROVIDER_ITEMS);
|
|
60
112
|
activity.skipped += countProviderToolItems("openai", items, count);
|
|
61
113
|
for (let index = 0; index < count; index++) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
});
|
|
114
|
+
try {
|
|
115
|
+
openaiItem(items[index], index, activity);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// An item that cannot be read is skipped; the others are still recorded.
|
|
119
|
+
activity.skipped++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function openaiItem(item, position, activity) {
|
|
124
|
+
if (!isItem(item))
|
|
125
|
+
return;
|
|
126
|
+
const callId = text(item.id);
|
|
127
|
+
switch (item.type) {
|
|
128
|
+
case "mcp_call": {
|
|
129
|
+
const name = text(item.name);
|
|
130
|
+
if (!name) {
|
|
131
|
+
activity.skipped++;
|
|
81
132
|
break;
|
|
82
133
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
134
|
+
const failed = item.error !== undefined && item.error !== null;
|
|
135
|
+
const detail = failed ? errorText(item.error) : undefined;
|
|
136
|
+
activity.calls.push({
|
|
137
|
+
name,
|
|
138
|
+
callId,
|
|
139
|
+
position,
|
|
140
|
+
server: text(item.server_label),
|
|
141
|
+
arguments: jsonArguments(item.arguments),
|
|
142
|
+
...(item.output !== undefined && item.output !== null ? { result: item.output } : {}),
|
|
143
|
+
...(failed ? { errorType: "mcp_error" } : {}),
|
|
144
|
+
...(detail !== undefined ? { errorText: detail } : {}),
|
|
145
|
+
});
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
case "mcp_list_tools": {
|
|
149
|
+
const server = text(item.server_label);
|
|
150
|
+
if (!server || !Array.isArray(item.tools)) {
|
|
151
|
+
activity.skipped++;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
const definitions = [];
|
|
155
|
+
const definitionCount = Math.min(item.tools.length, MAX_PROVIDER_DEFINITIONS);
|
|
156
|
+
activity.skipped += item.tools.length - definitionCount;
|
|
157
|
+
for (let index = 0; index < definitionCount; index++) {
|
|
158
|
+
try {
|
|
93
159
|
const tool = item.tools[index];
|
|
94
160
|
if (!isItem(tool))
|
|
95
161
|
continue;
|
|
162
|
+
// A null field is left out, as absent, so both SDKs digest the same definition.
|
|
96
163
|
definitions.push({
|
|
97
164
|
type: "function",
|
|
98
|
-
...(tool.name
|
|
99
|
-
...(tool.description
|
|
100
|
-
...(tool.input_schema
|
|
101
|
-
...(tool.annotations
|
|
165
|
+
...(tool.name != null ? { name: tool.name } : {}),
|
|
166
|
+
...(tool.description != null ? { description: tool.description } : {}),
|
|
167
|
+
...(tool.input_schema != null ? { parameters: tool.input_schema } : {}),
|
|
168
|
+
...(tool.annotations != null ? { annotations: tool.annotations } : {}),
|
|
102
169
|
});
|
|
103
170
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
case "web_search_call":
|
|
112
|
-
case "file_search_call":
|
|
113
|
-
case "code_interpreter_call": {
|
|
114
|
-
const name = item.type.slice(0, -"_call".length);
|
|
115
|
-
const failed = item.status === "failed" ? { errorType: "failed" } : {};
|
|
116
|
-
if (item.type === "web_search_call")
|
|
117
|
-
activity.calls.push({ name, callId, arguments: item.action, ...failed });
|
|
118
|
-
else if (item.type === "file_search_call")
|
|
119
|
-
activity.calls.push({
|
|
120
|
-
name,
|
|
121
|
-
callId,
|
|
122
|
-
arguments: { queries: item.queries },
|
|
123
|
-
...(item.results !== undefined && item.results !== null
|
|
124
|
-
? { result: item.results }
|
|
125
|
-
: {}),
|
|
126
|
-
...failed,
|
|
127
|
-
});
|
|
128
|
-
else
|
|
129
|
-
activity.calls.push({
|
|
130
|
-
name,
|
|
131
|
-
callId,
|
|
132
|
-
arguments: { code: item.code, container_id: item.container_id },
|
|
133
|
-
...(item.outputs !== undefined && item.outputs !== null
|
|
134
|
-
? { result: item.outputs }
|
|
135
|
-
: {}),
|
|
136
|
-
...failed,
|
|
137
|
-
});
|
|
138
|
-
break;
|
|
171
|
+
catch {
|
|
172
|
+
// A definition that cannot be read is skipped; the rest of the listing is kept.
|
|
173
|
+
activity.skipped++;
|
|
174
|
+
}
|
|
139
175
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
176
|
+
activity.listings.push({
|
|
177
|
+
server,
|
|
178
|
+
definitions,
|
|
179
|
+
...(item.error !== undefined && item.error !== null ? { errorType: "mcp_error" } : {}),
|
|
180
|
+
});
|
|
181
|
+
break;
|
|
143
182
|
}
|
|
183
|
+
case "web_search_call":
|
|
184
|
+
case "file_search_call":
|
|
185
|
+
case "code_interpreter_call": {
|
|
186
|
+
const name = item.type.slice(0, -"_call".length);
|
|
187
|
+
const failed = item.status === "failed" ? { errorType: "failed" } : {};
|
|
188
|
+
if (item.type === "web_search_call")
|
|
189
|
+
activity.calls.push({ name, callId, position, arguments: item.action, ...failed });
|
|
190
|
+
else if (item.type === "file_search_call")
|
|
191
|
+
activity.calls.push({
|
|
192
|
+
name,
|
|
193
|
+
callId,
|
|
194
|
+
position,
|
|
195
|
+
arguments: { queries: item.queries },
|
|
196
|
+
...(item.results !== undefined && item.results !== null ? { result: item.results } : {}),
|
|
197
|
+
...failed,
|
|
198
|
+
});
|
|
199
|
+
else
|
|
200
|
+
activity.calls.push({
|
|
201
|
+
name,
|
|
202
|
+
callId,
|
|
203
|
+
position,
|
|
204
|
+
arguments: { code: item.code, container_id: item.container_id },
|
|
205
|
+
...(item.outputs !== undefined && item.outputs !== null ? { result: item.outputs } : {}),
|
|
206
|
+
...failed,
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
// Messages, reasoning, approval requests and other items are not executed tools.
|
|
212
|
+
break;
|
|
144
213
|
}
|
|
145
214
|
}
|
|
146
215
|
/** Anthropic Messages `content` blocks: a use block paired with the result block that names it. */
|
|
@@ -149,47 +218,64 @@ function anthropicCalls(blocks, activity) {
|
|
|
149
218
|
const count = Math.min(blocks.length, MAX_PROVIDER_ITEMS);
|
|
150
219
|
const truncated = blocks.length > MAX_PROVIDER_ITEMS;
|
|
151
220
|
activity.skipped += countProviderToolItems("anthropic", blocks, count);
|
|
221
|
+
// Each block is read once; one that cannot be read is skipped and the rest still recorded.
|
|
222
|
+
const readable = [];
|
|
152
223
|
for (let index = 0; index < count; index++) {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
224
|
+
try {
|
|
225
|
+
const block = blocks[index];
|
|
226
|
+
if (isItem(block) &&
|
|
227
|
+
typeof block.type === "string" &&
|
|
228
|
+
block.type.endsWith("_tool_result") &&
|
|
229
|
+
typeof block.tool_use_id === "string")
|
|
230
|
+
results.set(block.tool_use_id, block);
|
|
231
|
+
readable.push(block);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
activity.skipped++;
|
|
235
|
+
readable.push(undefined);
|
|
236
|
+
}
|
|
159
237
|
}
|
|
160
238
|
for (let index = 0; index < count; index++) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
continue;
|
|
164
|
-
const name = text(block.name);
|
|
165
|
-
const callId = text(block.id);
|
|
166
|
-
if (!name) {
|
|
167
|
-
activity.skipped++;
|
|
168
|
-
continue;
|
|
239
|
+
try {
|
|
240
|
+
anthropicBlock(readable[index], index, results, truncated, activity);
|
|
169
241
|
}
|
|
170
|
-
|
|
171
|
-
// When the response was truncated, an unmatched use block may have its result outside the
|
|
172
|
-
// bounded prefix. Do not export it as a successful call with a missing result.
|
|
173
|
-
if (truncated && result === undefined) {
|
|
242
|
+
catch {
|
|
174
243
|
activity.skipped++;
|
|
175
|
-
continue;
|
|
176
244
|
}
|
|
177
|
-
const content = result?.content;
|
|
178
|
-
let errorType;
|
|
179
|
-
if (result?.is_error === true)
|
|
180
|
-
errorType = "mcp_error";
|
|
181
|
-
else if (isItem(content) && typeof content.type === "string" && content.type.endsWith("_error"))
|
|
182
|
-
errorType = errorCode(content.error_code);
|
|
183
|
-
activity.calls.push({
|
|
184
|
-
name,
|
|
185
|
-
callId,
|
|
186
|
-
...(block.type === "mcp_tool_use" ? { server: text(block.server_name) } : {}),
|
|
187
|
-
arguments: block.input,
|
|
188
|
-
...(content !== undefined ? { result: content } : {}),
|
|
189
|
-
...(errorType ? { errorType } : {}),
|
|
190
|
-
});
|
|
191
245
|
}
|
|
192
246
|
}
|
|
247
|
+
function anthropicBlock(block, position, results, truncated, activity) {
|
|
248
|
+
if (!isItem(block) || (block.type !== "mcp_tool_use" && block.type !== "server_tool_use"))
|
|
249
|
+
return;
|
|
250
|
+
const name = text(block.name);
|
|
251
|
+
const callId = text(block.id);
|
|
252
|
+
if (!name) {
|
|
253
|
+
activity.skipped++;
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const result = callId === undefined ? undefined : results.get(callId);
|
|
257
|
+
// When the response was truncated, an unmatched use block may have its result outside the
|
|
258
|
+
// bounded prefix. Do not export it as a successful call with a missing result.
|
|
259
|
+
if (truncated && result === undefined) {
|
|
260
|
+
activity.skipped++;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const content = result?.content;
|
|
264
|
+
let errorType;
|
|
265
|
+
if (result?.is_error === true)
|
|
266
|
+
errorType = "mcp_error";
|
|
267
|
+
else if (isItem(content) && typeof content.type === "string" && content.type.endsWith("_error"))
|
|
268
|
+
errorType = errorCode(content.error_code);
|
|
269
|
+
activity.calls.push({
|
|
270
|
+
name,
|
|
271
|
+
callId,
|
|
272
|
+
position,
|
|
273
|
+
...(block.type === "mcp_tool_use" ? { server: text(block.server_name) } : {}),
|
|
274
|
+
arguments: block.input,
|
|
275
|
+
...(content !== undefined ? { result: content } : {}),
|
|
276
|
+
...(errorType ? { errorType } : {}),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
193
279
|
/**
|
|
194
280
|
* Reads the hosted tool calls out of a provider response: the `output` items of an OpenAI
|
|
195
281
|
* Responses API response, or the `content` blocks of an Anthropic Messages API response. An array
|
package/dist/safety.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { type Span } from "@opentelemetry/api";
|
|
2
2
|
export declare function noopSpan(): Span;
|
|
3
3
|
export declare function safeSpan(source: Span, failed: () => void): Span;
|
|
4
|
+
/** The bounds `encodeContent` enforces while it copies a value. */
|
|
5
|
+
export interface EncodeLimits {
|
|
6
|
+
bytes: number;
|
|
7
|
+
nodes: number;
|
|
8
|
+
depth: number;
|
|
9
|
+
}
|
|
4
10
|
/** Validate a bounded data tree without invoking toJSON or property getters. */
|
|
5
|
-
export declare function encodeContent(value: unknown): string;
|
|
11
|
+
export declare function encodeContent(value: unknown, limits?: EncodeLimits): string;
|
|
6
12
|
/** Bounded conservative accounting for the record data retained by our queue, not total process RSS. */
|
|
7
13
|
export declare function estimateRecordBytes(value: unknown, limit: number): number;
|
package/dist/safety.js
CHANGED
|
@@ -89,21 +89,23 @@ class SafeSpan {
|
|
|
89
89
|
export function safeSpan(source, failed) {
|
|
90
90
|
return new SafeSpan(source, failed);
|
|
91
91
|
}
|
|
92
|
+
/** One content attribute's bounds. */
|
|
93
|
+
const contentLimits = { bytes: MAX_CONTENT_BYTES, nodes: 16384, depth: 32 };
|
|
92
94
|
/** Validate a bounded data tree without invoking toJSON or property getters. */
|
|
93
|
-
export function encodeContent(value) {
|
|
95
|
+
export function encodeContent(value, limits = contentLimits) {
|
|
94
96
|
let nodes = 0;
|
|
95
97
|
let bytes = 0;
|
|
96
98
|
const ancestors = new Set();
|
|
97
99
|
const charge = (amount) => {
|
|
98
100
|
bytes += amount;
|
|
99
|
-
if (bytes >
|
|
101
|
+
if (bytes > limits.bytes)
|
|
100
102
|
throw new RangeError("Content limit exceeded");
|
|
101
103
|
};
|
|
102
104
|
const visit = (item, depth) => {
|
|
103
|
-
if (++nodes >
|
|
105
|
+
if (++nodes > limits.nodes || depth > limits.depth)
|
|
104
106
|
throw new RangeError("Content complexity limit exceeded");
|
|
105
107
|
if (typeof item === "string") {
|
|
106
|
-
if (item.length >
|
|
108
|
+
if (item.length > limits.bytes)
|
|
107
109
|
throw new RangeError("Content limit exceeded");
|
|
108
110
|
charge(Buffer.byteLength(JSON.stringify(item)));
|
|
109
111
|
return item;
|
|
@@ -128,9 +130,9 @@ export function encodeContent(value) {
|
|
|
128
130
|
const result = array ? [] : Object.create(null);
|
|
129
131
|
// Own descriptors avoid executing application accessors during capture.
|
|
130
132
|
const keys = array
|
|
131
|
-
? Array.from({ length: Math.min(item.length,
|
|
133
|
+
? Array.from({ length: Math.min(item.length, limits.nodes + 1) }, (_, i) => String(i))
|
|
132
134
|
: Object.keys(item);
|
|
133
|
-
if (keys.length >
|
|
135
|
+
if (keys.length > limits.nodes)
|
|
134
136
|
throw new RangeError("Content complexity limit exceeded");
|
|
135
137
|
for (const key of keys) {
|
|
136
138
|
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
@@ -147,7 +149,7 @@ export function encodeContent(value) {
|
|
|
147
149
|
return result;
|
|
148
150
|
};
|
|
149
151
|
const encoded = JSON.stringify(visit(value, 0));
|
|
150
|
-
if (Buffer.byteLength(encoded) >
|
|
152
|
+
if (Buffer.byteLength(encoded) > limits.bytes)
|
|
151
153
|
throw new RangeError("Content limit exceeded");
|
|
152
154
|
return encoded;
|
|
153
155
|
}
|
package/dist/setup/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { parseArgs } from "node:util";
|
|
|
4
4
|
import { FileSetupCheckpointAdapter, setupRunId } from "./checkpoint.js";
|
|
5
5
|
import { SetupBackendAdapter } from "./backend.js";
|
|
6
6
|
import { detectSetupProject } from "./detect.js";
|
|
7
|
+
import { loadCommand, MissingPeerError } from "./peers.js";
|
|
7
8
|
import { renderHumanEvent, renderJsonlEvent, renderPlainEvent, selectSetupOutputMode, } from "./render.js";
|
|
8
9
|
import { runSetup } from "./runner.js";
|
|
9
10
|
import { SETUP_EVENT_CONTRACT_VERSION } from "./types.js";
|
|
@@ -11,9 +12,22 @@ const commands = new Set(["setup", "resume", "status", "claim"]);
|
|
|
11
12
|
// Additional commands live in ../cli and load lazily so the setup parser, its usage text and its
|
|
12
13
|
// JSONL error contract stay untouched for every other input. Add a command with one entry.
|
|
13
14
|
const extensions = new Map([
|
|
14
|
-
[
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
[
|
|
16
|
+
"eval",
|
|
17
|
+
async () => (await loadCommand("eval", () => import("../cli/eval.js"))).runEvalCommand(process.argv.slice(3)),
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
"login",
|
|
21
|
+
async () => (await loadCommand("login", () => import("../cli/login.js"))).runLoginCommand(process.argv.slice(3)),
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"mcp",
|
|
25
|
+
async () => (await loadCommand("mcp", () => import("../cli/mcp.js"))).runMcpCommand(process.argv.slice(3)),
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"listen",
|
|
29
|
+
async () => (await loadCommand("listen", () => import("../cli/listen.js"))).runListenCommand(process.argv.slice(3)),
|
|
30
|
+
],
|
|
17
31
|
]);
|
|
18
32
|
function writeEvent(event, mode, width) {
|
|
19
33
|
const line = mode === "jsonl"
|
|
@@ -26,8 +40,17 @@ function writeEvent(event, mode, width) {
|
|
|
26
40
|
}
|
|
27
41
|
async function main() {
|
|
28
42
|
const extension = extensions.get(process.argv[2] ?? "");
|
|
29
|
-
if (extension)
|
|
30
|
-
|
|
43
|
+
if (extension) {
|
|
44
|
+
try {
|
|
45
|
+
return await extension();
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (!(error instanceof MissingPeerError))
|
|
49
|
+
throw error;
|
|
50
|
+
process.stderr.write(`${error.message}\n`);
|
|
51
|
+
return 2;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
31
54
|
const agentRequested = process.argv.slice(2).includes("--agent");
|
|
32
55
|
let parsed;
|
|
33
56
|
try {
|
|
@@ -47,7 +70,7 @@ async function main() {
|
|
|
47
70
|
}
|
|
48
71
|
catch {
|
|
49
72
|
if (!agentRequested) {
|
|
50
|
-
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
73
|
+
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
51
74
|
return 2;
|
|
52
75
|
}
|
|
53
76
|
const event = {
|
|
@@ -78,7 +101,7 @@ async function main() {
|
|
|
78
101
|
process.stdout.write(`${renderJsonlEvent(event)}\n`);
|
|
79
102
|
return 2;
|
|
80
103
|
}
|
|
81
|
-
process.stdout.write("Usage: hue <setup|resume|status|claim|login|eval|mcp> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
104
|
+
process.stdout.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
82
105
|
return 0;
|
|
83
106
|
}
|
|
84
107
|
const command = parsed.positionals[0];
|
|
@@ -110,7 +133,7 @@ async function main() {
|
|
|
110
133
|
process.stdout.write(`${renderJsonlEvent(event)}\n`);
|
|
111
134
|
}
|
|
112
135
|
else
|
|
113
|
-
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
136
|
+
process.stderr.write("Usage: hue <setup|resume|status|claim|login|eval|mcp|listen> [--agent|--format human|plain|jsonl] [--project PATH] [--origin URL] [--restart]\n");
|
|
114
137
|
return 2;
|
|
115
138
|
}
|
|
116
139
|
const mode = selectSetupOutputMode({
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** A peer dependency of `@hue-run/sdk` that a command needs and the project has not installed. */
|
|
2
|
+
export declare class MissingPeerError extends Error {
|
|
3
|
+
readonly command: string;
|
|
4
|
+
readonly peer: string;
|
|
5
|
+
readonly range: string;
|
|
6
|
+
constructor(command: string, peer: string, range: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Loads a command module. When the import fails because one of the SDK's peers, such as `zod` or
|
|
10
|
+
* `@opentelemetry/api`, is not installed, the failure names that peer and how to add it instead
|
|
11
|
+
* of the runtime's resolution error; any other failure is rethrown unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadCommand<T>(command: string, load: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/** A peer dependency of `@hue-run/sdk` that a command needs and the project has not installed. */
|
|
3
|
+
export class MissingPeerError extends Error {
|
|
4
|
+
command;
|
|
5
|
+
peer;
|
|
6
|
+
range;
|
|
7
|
+
constructor(command, peer, range) {
|
|
8
|
+
super(`hue ${command} needs ${peer}, a peer dependency of @hue-run/sdk that this project has not installed. Install it with: npm install "${peer}@${range}" (or bun add, pnpm add or yarn add with the same argument)`);
|
|
9
|
+
this.command = command;
|
|
10
|
+
this.peer = peer;
|
|
11
|
+
this.range = range;
|
|
12
|
+
this.name = "MissingPeerError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** The range `@hue-run/sdk` declares for a peer, or undefined when `name` is not one of its peers. */
|
|
16
|
+
function peerRange(name) {
|
|
17
|
+
try {
|
|
18
|
+
const manifest = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
19
|
+
const range = manifest.peerDependencies?.[name];
|
|
20
|
+
return typeof range === "string" ? range : undefined;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Loads a command module. When the import fails because one of the SDK's peers, such as `zod` or
|
|
28
|
+
* `@opentelemetry/api`, is not installed, the failure names that peer and how to add it instead
|
|
29
|
+
* of the runtime's resolution error; any other failure is rethrown unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export async function loadCommand(command, load) {
|
|
32
|
+
try {
|
|
33
|
+
return await load();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Only a package that cannot be found at all; "Cannot find module" for a file or subpath of
|
|
37
|
+
// an installed package is another failure and keeps its own message.
|
|
38
|
+
const missing = /Cannot find package ['"]((?:@[^/'"]+\/)?[^/'"]+)['"]/.exec(error instanceof Error ? error.message : "")?.[1];
|
|
39
|
+
const range = missing === undefined ? undefined : peerRange(missing);
|
|
40
|
+
if (missing === undefined || range === undefined)
|
|
41
|
+
throw error;
|
|
42
|
+
throw new MissingPeerError(command, missing, range);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { ReadableSpan } from "@opentelemetry/sdk-trace";
|
|
2
2
|
import type { ReadableLogRecord, ReadWriteLogRecord } from "@opentelemetry/sdk-logs";
|
|
3
|
-
export declare function snapshotSpan(source: ReadableSpan, limit: number): {
|
|
3
|
+
export declare function snapshotSpan(source: ReadableSpan, limit: number, captureContent?: boolean): {
|
|
4
4
|
record: ReadableSpan;
|
|
5
5
|
bytes: number;
|
|
6
6
|
unresolvedResource: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare function snapshotLog(source: ReadableLogRecord, limit: number): {
|
|
8
|
+
export declare function snapshotLog(source: ReadableLogRecord, limit: number, captureContent?: boolean): {
|
|
9
9
|
record: ReadWriteLogRecord;
|
|
10
10
|
bytes: number;
|
|
11
11
|
unresolvedResource: boolean;
|