@hue-run/sdk 0.10.0 → 0.11.1

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/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 > MAX_CONTENT_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 > 16384 || depth > 32)
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 > MAX_CONTENT_BYTES)
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, 16385) }, (_, i) => String(i))
133
+ ? Array.from({ length: Math.min(item.length, limits.nodes + 1) }, (_, i) => String(i))
132
134
  : Object.keys(item);
133
- if (keys.length > 16384)
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) > MAX_CONTENT_BYTES)
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
- ["eval", async () => (await import("../cli/eval.js")).runEvalCommand(process.argv.slice(3))],
15
- ["login", async () => (await import("../cli/login.js")).runLoginCommand(process.argv.slice(3))],
16
- ["mcp", async () => (await import("../cli/mcp.js")).runMcpCommand(process.argv.slice(3))],
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
- return extension();
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
+ }
@@ -1,3 +1,15 @@
1
+ /**
2
+ * Removes credentials from free text a provider returned, such as an MCP call's error message,
3
+ * with the rules tool definitions use, extended for text: each `http`, `https`, `ws`, `wss` or
4
+ * `ftp` URL loses its userinfo and fragment and every query value becomes `[redacted]`, as a
5
+ * `url` field does (an unparseable one becomes `[redacted]`), and a URL with any other scheme
6
+ * becomes `[redacted]` when it has an `@`, `?` or `#`; a token with a known credential prefix
7
+ * (`hue_sk_`, `sk-`, `xoxb-`, `ya29.` and others), the credential after an authorization scheme
8
+ * (`Bearer`, `Basic`, `Token`), the whole value of an `Authorization` header, and the value of a
9
+ * `key=value` or `key: value` pair whose key names a credential (quoted, escaped-quoted or bare)
10
+ * become `[redacted]`.
11
+ */
12
+ export declare function scrubCredentialText(text: string): string;
1
13
  /**
2
14
  * Removes hosted-tool credentials from an exported attribute value, before the caller's `redact`.
3
15
  * Tool definitions come from OpenTelemetry GenAI (`gen_ai.tool.definitions`), AI SDK 6
@@ -18,3 +30,9 @@ export declare function scrubToolCredentials(key: string, value: unknown): unkno
18
30
  * already sets are kept.
19
31
  */
20
32
  export declare function withToolCatalogSummary<T extends Record<string, unknown>>(source: T): T;
33
+ /**
34
+ * The metadata-only summary of one JSON-encoded definition list: `hue.tool.names` and
35
+ * `hue.tool.definitions.sha256`, exactly as export summarizes a record's
36
+ * `gen_ai.tool.definitions`. Empty when the list cannot be summarized.
37
+ */
38
+ export declare function toolCatalogSummary(definitions: string): Record<string, string | string[]>;
@@ -72,6 +72,223 @@ function isCredentialKey(key) {
72
72
  normalized.endsWith("apikey") ||
73
73
  normalized.endsWith("credential"));
74
74
  }
75
+ /** A URL's `://` and everything after it up to whitespace, a quote or `<>`. A quoted value right
76
+ * after `=` (`?token="…"`) is part of the URL, so the whole value is replaced. */
77
+ const urlRest = /:\/\/(?:[^\s"'<>`]|(?<==)"[^"<>`\r\n]*"|(?<==)'[^'<>`\r\n]*'|(?<==)["'])+/y;
78
+ const isSchemeLetter = (code) => (code | 0x20) >= 0x61 && (code | 0x20) <= 0x7a;
79
+ /** `a-z`, `0-9`, `+`, `.` and `-`, case-insensitively. */
80
+ const isSchemeCharacter = (code) => isSchemeLetter(code) ||
81
+ (code >= 0x30 && code <= 0x39) ||
82
+ code === 0x2b ||
83
+ code === 0x2e ||
84
+ code === 0x2d;
85
+ /** Scrubs each URL in free text. Each `://` is found by search and its scheme read back from it:
86
+ * up to 64 scheme characters, starting at a letter, so a longer run before `://` still leaves a
87
+ * URL to scrub and a long run such as `a.a.a…` costs one pass. */
88
+ function scrubTextUrls(text, state) {
89
+ let result = "";
90
+ let copied = 0;
91
+ for (let index = text.indexOf("://"); index !== -1; index = text.indexOf("://", index + 1)) {
92
+ if (index < copied)
93
+ continue;
94
+ let start = index;
95
+ while (start > copied && index - start < 64 && isSchemeCharacter(text.charCodeAt(start - 1)))
96
+ start--;
97
+ while (start < index && !isSchemeLetter(text.charCodeAt(start)))
98
+ start++;
99
+ if (start === index)
100
+ continue;
101
+ urlRest.lastIndex = index;
102
+ const rest = urlRest.exec(text);
103
+ if (!rest)
104
+ continue;
105
+ const end = index + rest[0].length;
106
+ result += text.slice(copied, start) + scrubTextUrl(text.slice(start, end), state);
107
+ copied = end;
108
+ }
109
+ return result + text.slice(copied);
110
+ }
111
+ /** Schemes WHATWG parses as hierarchical, which both SDKs serialize alike. */
112
+ const specialScheme = /^(?:https?|wss?|ftp):/i;
113
+ /** A URL in free text. One with another scheme, which runtimes parse differently, is replaced
114
+ * whole when it could carry userinfo, a query or a fragment. */
115
+ function scrubTextUrl(url, state) {
116
+ if (specialScheme.test(url))
117
+ return scrubUrl(url, state);
118
+ return /[@?#]/.test(url) ? REDACTED : url;
119
+ }
120
+ /** A credential its own prefix identifies wherever it appears, `%` escapes included: Hue's API, MCP,
121
+ * world, attempt, simulation, setup, install and invocation tokens, and OpenAI and Anthropic
122
+ * (`sk-`), Stripe, Slack, Google OAuth, GitHub and GitLab ones. */
123
+ const prefixedToken = /\b(?:hue_(?:sk|mcp|world|attempt|sim|setup|install|inv)_|sk-|[rs]k_(?:live|test)_|xox[abcdoprs]-|xapp-|ya29\.|gh[opsur]_|github_pat_|glpat-)[a-z0-9_.~+/=%-]{8,}/gi;
124
+ /** An authorization scheme followed by its credential, as in an `Authorization` header, up to
125
+ * whitespace, a quote, a delimiter or a backslash. The credential cannot start with `=` or `:`, so
126
+ * `token = value` and `Token : value` are left to the key-value rule. */
127
+ const authorizationValue = /\b(bearer|basic|token)(\s+)[^\s"'`<>=:,;(){}[\]\\][^\s"'`<>,;(){}[\]\\]*/gi;
128
+ /** The key and separator of a `key=value` or `key: value` pair, the key optionally quoted, with a
129
+ * backslash-escaped quote too (JSON inside a string). A key starts where no key character precedes
130
+ * it, so each word is tried once and a long run stays linear. The value is not consumed, so a pair
131
+ * inside another pair's value (`error: token=…`) is found. */
132
+ const pairKey = /(\\?["']|)(?<![a-z0-9_-])([a-z0-9_-]+)\1(\s*[:=]\s*)/gi;
133
+ /** A quoted value to its closing quote on the same line, spaces and escaped quotes included, or
134
+ * one between backslash-escaped quotes, double or single. */
135
+ const quotedValue = /"(?:[^"\\\r\n]|\\[^\r\n])+"|'(?:[^'\\\r\n]|\\[^\r\n])+'|\\"(?:[^"\\\r\n]|\\[^"\r\n])+\\"|\\'(?:[^'\\\r\n]|\\[^'\r\n])+\\'/y;
136
+ /** An unquoted value, or one whose quote does not close on its line, up to whitespace, a quote or
137
+ * a delimiter; a value already replaced, or a scheme whose credential was, is left alone. */
138
+ const bareValue = /(\\?["']?)(?!\[redacted\]|%5Bredacted%5D|(?:bearer|basic|token)\s)[^\s"',;&})\]]+/iy;
139
+ /** An `Authorization` header's unquoted value: its scheme and the credential after it (`Bot …`,
140
+ * `OAuth1 …`), or a lone credential. One already replaced is left alone. */
141
+ const authorizationBare = /(\\?["']?)(?!\[redacted\]|%5Bredacted%5D)([^\s"',;})\]]+)(?:[ \t]+(?:\[redacted\]|[^\s"',;})\]]+))?/y;
142
+ function normalizedKey(key) {
143
+ return key.toLowerCase().replace(/[-_]/g, "");
144
+ }
145
+ /** A key naming a credential in free text: a tool definition's credential keys, any header ending
146
+ * in `Authorization`, and `Bearer` and `Basic`. */
147
+ function isTextCredentialKey(key) {
148
+ const normalized = normalizedKey(key);
149
+ return (isCredentialKey(key) ||
150
+ normalized.endsWith("authorization") ||
151
+ normalized === "bearer" ||
152
+ normalized === "basic");
153
+ }
154
+ /** `API key: …`: a credential named in two words, `key` right after `API`. */
155
+ const apiBefore = /(?:^|[^a-z0-9_])api[ \t]+$/i;
156
+ function isApiKeyPhrase(text, keyStart, key) {
157
+ return (key.toLowerCase() === "key" && apiBefore.test(text.slice(Math.max(0, keyStart - 16), keyStart)));
158
+ }
159
+ /** Whether a quote, or a backslash-escaped quote, opens at `index`. */
160
+ function opensQuote(text, index) {
161
+ const character = text[index];
162
+ return (character === '"' ||
163
+ character === "'" ||
164
+ text.startsWith('\\"', index) ||
165
+ text.startsWith("\\'", index));
166
+ }
167
+ /** The value of each pair whose key names a credential, without its quotes. A pair inside an
168
+ * earlier pair's value is kept when its value runs past that value. */
169
+ function pairSpans(text) {
170
+ const spans = [];
171
+ let covered = 0;
172
+ // The last `Authorization` value read: where it starts, where its first word ends and where it
173
+ // ends. A value starting inside that first word ends where it does, so each run is read once.
174
+ let authorization;
175
+ for (const match of text.matchAll(pairKey)) {
176
+ const start = match.index + match[0].length;
177
+ const key = match[2];
178
+ const keyStart = match.index + match[1].length;
179
+ if (!(isTextCredentialKey(key) || isApiKeyPhrase(text, keyStart, key)))
180
+ continue;
181
+ const nested = start < covered;
182
+ const isAuthorization = normalizedKey(key).endsWith("authorization");
183
+ // A value inside an earlier value ends where that value's run or quote ends, so it can run
184
+ // past it only by opening a quote (which may be the one that closes the earlier value) or,
185
+ // for `Authorization`, by the word after its scheme.
186
+ if (nested && !isAuthorization && !opensQuote(text, start))
187
+ continue;
188
+ quotedValue.lastIndex = start;
189
+ const quoted = quotedValue.exec(text);
190
+ let open;
191
+ let end;
192
+ if (quoted) {
193
+ open = quoted[0].startsWith("\\") ? 2 : 1;
194
+ end = start + quoted[0].length;
195
+ }
196
+ else if (isAuthorization) {
197
+ if (authorization &&
198
+ start > authorization.from &&
199
+ start < authorization.firstEnd &&
200
+ !opensQuote(text, start) &&
201
+ !text.startsWith("[redacted]", start) &&
202
+ !text.startsWith("%5Bredacted%5D", start)) {
203
+ open = 0;
204
+ end = authorization.end;
205
+ }
206
+ else {
207
+ authorizationBare.lastIndex = start;
208
+ const value = authorizationBare.exec(text);
209
+ if (!value)
210
+ continue;
211
+ open = value[1].length;
212
+ end = start + value[0].length;
213
+ authorization = { from: start, firstEnd: start + open + value[2].length, end };
214
+ }
215
+ }
216
+ else {
217
+ bareValue.lastIndex = start;
218
+ const value = bareValue.exec(text);
219
+ if (!value)
220
+ continue;
221
+ open = value[1].length;
222
+ end = start + value[0].length;
223
+ }
224
+ if (end <= covered)
225
+ continue;
226
+ spans.push([start + open, end - (quoted ? open : 0)]);
227
+ covered = end;
228
+ }
229
+ return spans;
230
+ }
231
+ /** The credential after each authorization scheme. A scheme word can end an earlier credential
232
+ * (`…~bearer SECRET`), so the search resumes at each credential, which is read at most once more. */
233
+ function authorizationSpans(text) {
234
+ const spans = [];
235
+ authorizationValue.lastIndex = 0;
236
+ for (let match = authorizationValue.exec(text); match; match = authorizationValue.exec(text)) {
237
+ const credential = match.index + match[1].length + match[2].length;
238
+ spans.push([credential, match.index + match[0].length]);
239
+ authorizationValue.lastIndex = credential;
240
+ }
241
+ return spans;
242
+ }
243
+ /** Each match of a global pattern. */
244
+ function matchSpans(text, pattern) {
245
+ return [...text.matchAll(pattern)].map((match) => [match.index, match.index + match[0].length]);
246
+ }
247
+ /** Replaces the union of the spans with `[redacted]`, each run of overlapping or touching spans
248
+ * once. */
249
+ function redactSpans(text, spans) {
250
+ spans.sort(([a, b], [c, d]) => a - c || b - d);
251
+ let result = "";
252
+ let copied = 0;
253
+ let run;
254
+ const flush = () => {
255
+ if (!run)
256
+ return;
257
+ result += `${text.slice(copied, run[0])}${REDACTED}`;
258
+ copied = run[1];
259
+ };
260
+ for (const [start, end] of spans) {
261
+ if (run && start <= run[1])
262
+ run[1] = Math.max(run[1], end);
263
+ else {
264
+ flush();
265
+ run = [start, end];
266
+ }
267
+ }
268
+ flush();
269
+ return result + text.slice(copied);
270
+ }
271
+ /**
272
+ * Removes credentials from free text a provider returned, such as an MCP call's error message,
273
+ * with the rules tool definitions use, extended for text: each `http`, `https`, `ws`, `wss` or
274
+ * `ftp` URL loses its userinfo and fragment and every query value becomes `[redacted]`, as a
275
+ * `url` field does (an unparseable one becomes `[redacted]`), and a URL with any other scheme
276
+ * becomes `[redacted]` when it has an `@`, `?` or `#`; a token with a known credential prefix
277
+ * (`hue_sk_`, `sk-`, `xoxb-`, `ya29.` and others), the credential after an authorization scheme
278
+ * (`Bearer`, `Basic`, `Token`), the whole value of an `Authorization` header, and the value of a
279
+ * `key=value` or `key: value` pair whose key names a credential (quoted, escaped-quoted or bare)
280
+ * become `[redacted]`.
281
+ */
282
+ export function scrubCredentialText(text) {
283
+ const scrubbed = scrubTextUrls(text, { changed: false });
284
+ // Every rule reads the same text and their matches are replaced together, so no rule's
285
+ // replacement can hide text another rule would have matched.
286
+ return redactSpans(scrubbed, [
287
+ ...pairSpans(scrubbed),
288
+ ...matchSpans(scrubbed, prefixedToken),
289
+ ...authorizationSpans(scrubbed),
290
+ ]);
291
+ }
75
292
  /** OpenInference records each tool as `llm.tools.{index}.tool.json_schema`. */
76
293
  const openInferenceTool = /^llm\.tools\.(\d+)\.tool\.json_schema$/;
77
294
  /**
@@ -272,3 +489,18 @@ export function withToolCatalogSummary(source) {
272
489
  }
273
490
  return { ...summary, ...source };
274
491
  }
492
+ /**
493
+ * The metadata-only summary of one JSON-encoded definition list: `hue.tool.names` and
494
+ * `hue.tool.definitions.sha256`, exactly as export summarizes a record's
495
+ * `gen_ai.tool.definitions`. Empty when the list cannot be summarized.
496
+ */
497
+ export function toolCatalogSummary(definitions) {
498
+ const summarized = withToolCatalogSummary({
499
+ "gen_ai.tool.definitions": definitions,
500
+ });
501
+ const summary = {};
502
+ for (const key of ["hue.tool.names", "hue.tool.definitions.sha256"])
503
+ if (key in summarized)
504
+ summary[key] = summarized[key];
505
+ return summary;
506
+ }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Package version shared by the instrumentation scope and the export User-Agent. */
2
- export declare const sdkVersion = "0.10.0";
2
+ export declare const sdkVersion = "0.11.1";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/write-version.mjs from package.json; do not edit by hand.
2
2
  /** Package version shared by the instrumentation scope and the export User-Agent. */
3
- export const sdkVersion = "0.10.0";
3
+ export const sdkVersion = "0.11.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hue-run/sdk",
3
- "version": "0.10.0",
3
+ "version": "0.11.1",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "publishConfig": {