@yak-io/javascript 0.10.0 → 0.10.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.
@@ -1,10 +1,21 @@
1
1
  /**
2
- * Generates a tool ID from a tool name using a 32-bit hash.
3
- * Format: `yt_<8-char-hex-hash>`.
2
+ * Convert a host tool name into a model-facing function name.
4
3
  *
5
- * The hash is deterministic so chat and voice always derive the same id for
6
- * the same tool name this is what lets the mint route, the iframe, and the
7
- * SDK all agree on which decorated id maps back to which host tool name.
4
+ * We sanitise to the allowed function-name charset (rather than hashing to an opaque
5
+ * `yt_<hex>`), so the model keeps the semantic signal of a readable name e.g.
6
+ * `orders.list` `orders_list`.
7
+ *
8
+ * This is a pure per-name transform; uniqueness across a manifest (and avoiding the
9
+ * reserved `redirect` name / `mcp__` namespace) is handled by the caller via
10
+ * {@link uniqueToolId}. Each runtime (the chat-ui iframe and this SDK's voice session)
11
+ * decorates and reverse-maps with its own manifest, so the ids never need to match
12
+ * across paths — only to be self-consistent within one.
8
13
  */
9
14
  export declare function generateToolId(originalName: string): string;
15
+ /**
16
+ * Resolve a collision-free, model-safe id for a host tool name, given the ids already
17
+ * taken in this manifest (seed `used` with reserved names like `redirect`). Host ids must
18
+ * never start with the `mcp__` namespace, which the dispatcher routes on.
19
+ */
20
+ export declare function uniqueToolId(originalName: string, used: Set<string>): string;
10
21
  //# sourceMappingURL=tool-name.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-name.d.ts","sourceRoot":"","sources":["../src/tool-name.ts"],"names":[],"mappings":"AAcA;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG3D"}
1
+ {"version":3,"file":"tool-name.d.ts","sourceRoot":"","sources":["../src/tool-name.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG3D;AAKD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAc5E"}
package/dist/tool-name.js CHANGED
@@ -1,24 +1,42 @@
1
- const TOOL_NAME_PREFIX = "yt_";
1
+ /** OpenAI/Realtime function names must match `^[a-zA-Z0-9_-]{1,64}$`. */
2
+ const MAX_TOOL_NAME_LENGTH = 64;
2
3
  /**
3
- * Simple 32-bit hash function (djb2 variant).
4
- * Produces consistent hash values for the same input string.
4
+ * Convert a host tool name into a model-facing function name.
5
+ *
6
+ * We sanitise to the allowed function-name charset (rather than hashing to an opaque
7
+ * `yt_<hex>`), so the model keeps the semantic signal of a readable name — e.g.
8
+ * `orders.list` → `orders_list`.
9
+ *
10
+ * This is a pure per-name transform; uniqueness across a manifest (and avoiding the
11
+ * reserved `redirect` name / `mcp__` namespace) is handled by the caller via
12
+ * {@link uniqueToolId}. Each runtime (the chat-ui iframe and this SDK's voice session)
13
+ * decorates and reverse-maps with its own manifest, so the ids never need to match
14
+ * across paths — only to be self-consistent within one.
5
15
  */
6
- function hash32(str) {
7
- let hash = 5381;
8
- for (let i = 0; i < str.length; i++) {
9
- hash = ((hash << 5) + hash + str.charCodeAt(i)) >>> 0;
10
- }
11
- return hash;
16
+ export function generateToolId(originalName) {
17
+ const sanitized = originalName.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, MAX_TOOL_NAME_LENGTH);
18
+ return sanitized.length > 0 ? sanitized : "tool";
12
19
  }
20
+ /** The MCP namespace prefix the voice/chat dispatchers route on. */
21
+ const MCP_NAMESPACE_PREFIX = "mcp__";
13
22
  /**
14
- * Generates a tool ID from a tool name using a 32-bit hash.
15
- * Format: `yt_<8-char-hex-hash>`.
16
- *
17
- * The hash is deterministic so chat and voice always derive the same id for
18
- * the same tool name — this is what lets the mint route, the iframe, and the
19
- * SDK all agree on which decorated id maps back to which host tool name.
23
+ * Resolve a collision-free, model-safe id for a host tool name, given the ids already
24
+ * taken in this manifest (seed `used` with reserved names like `redirect`). Host ids must
25
+ * never start with the `mcp__` namespace, which the dispatcher routes on.
20
26
  */
21
- export function generateToolId(originalName) {
22
- const hashValue = hash32(originalName);
23
- return `${TOOL_NAME_PREFIX}${hashValue.toString(16).padStart(8, "0")}`;
27
+ export function uniqueToolId(originalName, used) {
28
+ let base = generateToolId(originalName);
29
+ if (base.startsWith(MCP_NAMESPACE_PREFIX)) {
30
+ base = `t_${base}`.slice(0, MAX_TOOL_NAME_LENGTH);
31
+ }
32
+ if (!used.has(base))
33
+ return base;
34
+ for (let i = 2; i < 1000; i++) {
35
+ const suffix = `_${i}`;
36
+ const candidate = `${base.slice(0, MAX_TOOL_NAME_LENGTH - suffix.length)}${suffix}`;
37
+ if (!used.has(candidate))
38
+ return candidate;
39
+ }
40
+ // Pathological fallback — vanishingly unlikely in practice.
41
+ return `${base.slice(0, 56)}_${used.size}`;
24
42
  }
@@ -81,11 +81,11 @@ export declare class YakVoiceSession {
81
81
  private execMcpTool;
82
82
  private mintToken;
83
83
  /**
84
- * Decorate the host's tool manifest with hashed ids and populate
85
- * `this.toolNameById` for reverse lookup. Mirrors the decoration the chat-ui
86
- * iframe applies before sending tools to `/api/chat`. GraphQL/REST tools are
87
- * ordinary manifest entries here (contributed by their adapters), so no
88
- * special-casing is needed.
84
+ * Decorate the host's tool manifest with readable, collision-free model-facing ids
85
+ * and populate `this.toolNameById` for reverse lookup. Mirrors the decoration the
86
+ * chat-ui iframe applies before sending tools to `/api/chat`. GraphQL/REST tools are
87
+ * ordinary manifest entries here (contributed by their adapters), so no special-casing
88
+ * is needed.
89
89
  */
90
90
  private buildDecoratedManifest;
91
91
  private exchangeSdp;
@@ -1 +1 @@
1
- {"version":3,"file":"voice-session.d.ts","sourceRoot":"","sources":["../src/voice-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC;CACF;AAiBD,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,eAAe,CAA6B;IACpD,6EAA6E;IAC7E,OAAO,CAAC,KAAK,CAAkC;IAC/C;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAA6B;gBAErC,MAAM,EAAE,qBAAqB;IAKzC;;;;OAIG;IACH,OAAO,KAAK,SAAS,GAEpB;IAED,0DAA0D;IACnD,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIzD,QAAQ,IAAI,YAAY;IAI/B;;;OAGG;IACI,YAAY,IAAI,MAAM;IAItB,aAAa,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAO9D;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwJnC,oDAAoD;IACvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,2FAA2F;IACpF,OAAO,IAAI,IAAI;IAWtB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,mBAAmB;YAeb,aAAa;IA8B3B;;;;OAIG;YACW,WAAW;YAwBX,SAAS;IAsBvB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;YAchB,WAAW;IAmBzB,OAAO,CAAC,kBAAkB;YAWZ,gBAAgB;YA2BhB,QAAQ;YAuCR,QAAQ;IAMtB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,cAAc;CAevB"}
1
+ {"version":3,"file":"voice-session.d.ts","sourceRoot":"","sources":["../src/voice-session.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAKL,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC;CACF;AAiBD,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;AAEjE,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC;;;OAGG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAiED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,SAAS,CAAiC;IAClD,OAAO,CAAC,eAAe,CAA6B;IACpD,6EAA6E;IAC7E,OAAO,CAAC,KAAK,CAAkC;IAC/C;;;;OAIG;IACH,OAAO,CAAC,YAAY,CAA6B;gBAErC,MAAM,EAAE,qBAAqB;IAKzC;;;;OAIG;IACH,OAAO,KAAK,SAAS,GAEpB;IAED,0DAA0D;IACnD,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI;IAIzD,QAAQ,IAAI,YAAY;IAI/B;;;OAGG;IACI,YAAY,IAAI,MAAM;IAItB,aAAa,CAAC,QAAQ,EAAE,kBAAkB,GAAG,MAAM,IAAI;IAO9D;;;OAGG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAwJnC,oDAAoD;IACvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,2FAA2F;IACpF,OAAO,IAAI,IAAI;IAWtB,OAAO,CAAC,mBAAmB;IAa3B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,mBAAmB;YAeb,aAAa;IA8B3B;;;;OAIG;YACW,WAAW;YAwBX,SAAS;IAsBvB;;;;;;OAMG;IACH,OAAO,CAAC,sBAAsB;YAgBhB,WAAW;IAmBzB,OAAO,CAAC,kBAAkB;YAWZ,gBAAgB;YA2BhB,QAAQ;YAuCR,QAAQ;IAMtB,OAAO,CAAC,QAAQ;IAahB,OAAO,CAAC,sBAAsB;IAQ9B,OAAO,CAAC,cAAc;CAevB"}
@@ -1,6 +1,6 @@
1
1
  import { logger } from "./logger.js";
2
2
  import { extractPageContext } from "./page-context.js";
3
- import { generateToolId } from "./tool-name.js";
3
+ import { uniqueToolId } from "./tool-name.js";
4
4
  import { handleRealtimeMessage, INITIAL_VOICE_MACHINE, voiceReducer, } from "./voice-machine.js";
5
5
  const DEFAULT_REALTIME_MODEL = "gpt-realtime";
6
6
  const REALTIME_CALLS_URL = "https://api.openai.com/v1/realtime/calls";
@@ -296,7 +296,7 @@ export class YakVoiceSession {
296
296
  }
297
297
  }
298
298
  async routeToolCall(idOrName, args) {
299
- // The model calls us back using the decorated id (e.g. yt_abc12345).
299
+ // The model calls us back using the decorated id (e.g. orders_list).
300
300
  // Resolve it to the original host tool name; fall back to the raw value
301
301
  // (it might be `redirect` or some non-decorated name).
302
302
  const name = this.toolNameById.get(idOrName) ?? idOrName;
@@ -372,16 +372,18 @@ export class YakVoiceSession {
372
372
  return (await res.json());
373
373
  }
374
374
  /**
375
- * Decorate the host's tool manifest with hashed ids and populate
376
- * `this.toolNameById` for reverse lookup. Mirrors the decoration the chat-ui
377
- * iframe applies before sending tools to `/api/chat`. GraphQL/REST tools are
378
- * ordinary manifest entries here (contributed by their adapters), so no
379
- * special-casing is needed.
375
+ * Decorate the host's tool manifest with readable, collision-free model-facing ids
376
+ * and populate `this.toolNameById` for reverse lookup. Mirrors the decoration the
377
+ * chat-ui iframe applies before sending tools to `/api/chat`. GraphQL/REST tools are
378
+ * ordinary manifest entries here (contributed by their adapters), so no special-casing
379
+ * is needed.
380
380
  */
381
381
  buildDecoratedManifest(chatConfig) {
382
382
  this.toolNameById.clear();
383
+ const used = new Set(["redirect"]);
383
384
  const decoratedHostTools = (chatConfig?.tools?.tools ?? []).map((t) => {
384
- const id = generateToolId(t.name);
385
+ const id = uniqueToolId(t.name, used);
386
+ used.add(id);
385
387
  this.toolNameById.set(id, t.name);
386
388
  return { ...t, id };
387
389
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yak-io/javascript",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Core JavaScript SDK for embedding yak chatbot",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",