@oyehello/chat 0.5.1 → 0.5.2

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 CHANGED
@@ -74,23 +74,33 @@ await chat.sendMessage('Hello');
74
74
 
75
75
  ## Client tools (browser actions)
76
76
 
77
- An agent can call browser-side tools your page executes. Register a handler per tool; a tool
78
- reaches the agent only when it is (a) enabled for that agent in the console AND (b) registered here:
77
+ The SDK supports two browser-side tools: `navigate` (go to a path on the current site) and `openUrl`
78
+ (open a URL, by default in a new tab). Once a tool is enabled for the agent in the console **and** the
79
+ target passes the agent's navigate allowlist (also set in the console), the SDK executes it itself —
80
+ **no host integration required**:
81
+ ```html
82
+ <script type="module">import '@oyehello/chat';</script>
83
+ <oyehello-chat api-key="ohk_pub_…" agent-id="agt_…" base-url="https://api.oyehello.ai"></oyehello-chat>
84
+ ```
85
+ That's it — a plain embed with no JS wiring can navigate the visitor or open a link when the agent calls
86
+ the tool. `navigate` calls `window.location.assign(path)`; `openUrl` calls `window.open(url, '_blank')`
87
+ (pass `newTab: false` from the agent to open in the same tab instead).
88
+
89
+ A host page MAY still register its own `navigate`/`openUrl` handler to override the default behavior
90
+ (e.g. push through a client-side router instead of a full navigation):
79
91
  ```js
80
92
  const chat = createChatClient({ apiKey, agentId, baseUrl });
81
93
  chat.registerTools({
82
94
  navigate: ({ path }) => router.push(path),
83
- fillField: ({ selector, value }) => { /* your DOM logic */ },
84
95
  });
85
96
  // or on the web component: document.querySelector('oyehello-chat').registerTools({ … })
86
97
  ```
87
- Register handlers BEFORE `start()` — only tools registered at open are offered to the agent that session.
98
+ Register handlers BEFORE `start()`. A host-registered handler always takes priority over the SDK's
99
+ built-in; any other tool name has no handler and fails cleanly.
88
100
 
89
- > **Security.** `navigate`/`openUrl` targets can be constrained to an allowlist in the agent's console
90
- > settings. The other tools (`click`, `fillField`, `readPageContext`, `custom`) receive
91
- > model-generated arguments with no server-side restriction: **your handler is the trust boundary.**
92
- > Because a visitor can influence the conversation, treat these as capable of arbitrary DOM action —
93
- > validate selectors/values and scope what each handler can do before enabling them.
101
+ > **Security.** Navigation targets (both the SDK's built-in execution and a host-overridden handler)
102
+ > are validated against the agent's navigate allowlist **server-side** before the tool call ever
103
+ > reaches the browser — the SDK/host is not the trust boundary for where a visitor can be sent.
94
104
 
95
105
  ## Requirements
96
106
  - `api-key` is a **publishable** key (`ohk_pub_…`) created in the oyehello console.
package/dist/index.cjs CHANGED
@@ -314,6 +314,29 @@ function installSpaHooks(onNav) {
314
314
  // src/client.ts
315
315
  var seq = 0;
316
316
  var uid = (p) => `${p}_${Date.now().toString(36)}_${(seq++).toString(36)}`;
317
+ var isSafeRelativePath = (p) => p.startsWith("/") && !p.startsWith("//");
318
+ var isSafeHttpUrl = (u) => {
319
+ try {
320
+ const parsed = new URL(u, window.location.origin);
321
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
322
+ } catch {
323
+ return false;
324
+ }
325
+ };
326
+ var BUILTIN_TOOL_HANDLERS = {
327
+ navigate: (args) => {
328
+ const path = String(args.path ?? "");
329
+ if (!isSafeRelativePath(path)) return { ok: false, error: "unsafe navigate path" };
330
+ window.location.assign(path);
331
+ return { ok: true };
332
+ },
333
+ openUrl: (args) => {
334
+ const url = String(args.url ?? "");
335
+ if (!isSafeHttpUrl(url)) return { ok: false, error: "unsafe url" };
336
+ window.open(url, args.newTab === false ? "_self" : "_blank", "noopener,noreferrer");
337
+ return { ok: true };
338
+ }
339
+ };
317
340
  function createChatClient(options) {
318
341
  const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
319
342
  const ES = options.eventSource ?? globalThis.EventSource;
@@ -472,7 +495,7 @@ function createChatClient(options) {
472
495
  } else if (ev.type === "tool_call") {
473
496
  if (seenCallIds.has(ev.callId)) return;
474
497
  seenCallIds.add(ev.callId);
475
- const handler = toolHandlers.get(ev.name);
498
+ const handler = toolHandlers.get(ev.name) ?? BUILTIN_TOOL_HANDLERS[ev.name];
476
499
  void (async () => {
477
500
  if (!session) return;
478
501
  const base = { baseUrl: options.baseUrl, sessionId: session.sessionId, sessionToken: session.sessionToken, callId: ev.callId, fetchImpl };
@@ -570,7 +593,8 @@ function createChatClient(options) {
570
593
  return;
571
594
  }
572
595
  if (deviceFingerprint === void 0) deviceFingerprint = await computeDeviceFingerprint().catch(() => null);
573
- session = await openSession({ baseUrl: options.baseUrl, apiKey: options.apiKey, agentId: options.agentId, user: options.user, clientTools: Array.from(toolHandlers.keys()), fingerprint: deviceFingerprint ?? void 0, fetchImpl });
596
+ const advertisedTools = /* @__PURE__ */ new Set([...toolHandlers.keys(), ...Object.keys(BUILTIN_TOOL_HANDLERS)]);
597
+ session = await openSession({ baseUrl: options.baseUrl, apiKey: options.apiKey, agentId: options.agentId, user: options.user, clientTools: Array.from(advertisedTools), fingerprint: deviceFingerprint ?? void 0, fetchImpl });
574
598
  persistSession(session);
575
599
  if (!warnedProtocol && typeof session.protocol === "number" && session.protocol !== CHAT_PROTOCOL_VERSION) {
576
600
  warnedProtocol = true;