@yanlinglabs/winter-agent-sdk 0.0.35 → 0.0.38

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
@@ -128,6 +128,37 @@ runtime's own — same descriptions, same input schemas, same inner-call prompts
128
128
  Withdraw either with `disallowedTools`. `WebSearch` can also be switched off at the backend with
129
129
  `web.search.enabled: false`.
130
130
 
131
+ - **`Search`** (opt-in, unreleased) takes `{query}` and returns Exa's answer mode: a written answer
132
+ and the pages it came from, in one call. It is offered only when `Options.tools` names it and
133
+ `web.search.authRef` names a key (the answer endpoint has no anonymous tier). The cited urls pass
134
+ the same `blockedDomains` floor, and a withheld source is counted in the result.
135
+
136
+ All three report the icon of each site they name to the HOST only, as `winter_site_icons:
137
+ [{url, icon_url}]` on the host-facing `tool_result` block. The model never sees it.
138
+
139
+ ### Shaping the tool surface (0.0.38)
140
+
141
+ - **`Options.tools`** — claude's own option: the built-in tool set by name, or the `claude_code`
142
+ preset (the same as leaving it out). A visibility list: a built-in left out is not advertised, not
143
+ searchable, and refused at dispatch. MCP servers' tools are never filtered by it. Without `ToolSearch`
144
+ in it, nothing is deferred.
145
+ - **`Options.deferTools`** — tools that start deferred while Tool Search is active (`toolSearchEnabled`),
146
+ loaded through `ToolSearch` on first use. This is the only way a built-in defers.
147
+ - **`McpSdkServerConfig.toolNames`** — plain names for an in-process server's tools (`{ browser:
148
+ "Browser" }`). The model, the transcript, hooks and `canUseTool` all see the plain name. The call still
149
+ reaches the host as `sdk_mcp_call` with the server's own tool name, and the tool still defers like an
150
+ MCP tool. The `mcp__<server>__<tool>` spelling stays an equivalent identity for permission rules,
151
+ `disallowedTools` and hook matchers; when one of those names it, the call is evaluated under that
152
+ spelling, as for an alias. A plain name that collides with another tool refuses the session. The old
153
+ spelling also selects the tool in a model call, `ToolSearch`'s `select:` and an agent definition's
154
+ `tools`.
155
+ - **`Options.legacyToolNames`** — `{ <old name>: <current tool name> }` for a host's own renamed tool:
156
+ the old name keeps working in calls, `select:`, agent definitions, rules, `disallowedTools` and hook
157
+ matchers.
158
+ - **`Options.reservedMcpServerNames`** — server names only the host's own `type: "sdk"` servers may
159
+ take; any other server under one (settings, project, plugin, explicit non-sdk, `mcp_set_servers`) is
160
+ refused, and an agent definition's inline server is renamed.
161
+
131
162
  - **`Options.web`** — `search.enabled`, `search.authRef` (the backend key, used only once the
132
163
  anonymous tier is exhausted), `search.maxSearchesPerCall` / `search.anonymousMaxSearchesPerCall`,
133
164
  `fetch.digestModel` / `fetch.authRef` (the page-digest model and its own credential),
package/dist/index.js CHANGED
@@ -419,7 +419,8 @@ function toWireMcpServers(servers) {
419
419
  type: "sdk",
420
420
  name: cfg.name,
421
421
  ...cfg.timeout !== undefined ? { timeout: cfg.timeout } : {},
422
- ...isWinterMcpServerInstance(cfg.instance) ? { tools: cfg.instance.listTools() } : {}
422
+ ...isWinterMcpServerInstance(cfg.instance) ? { tools: cfg.instance.listTools() } : {},
423
+ ...cfg.toolNames !== undefined ? { toolNames: { ...cfg.toolNames } } : {}
423
424
  } : cfg;
424
425
  }
425
426
  return out;
@@ -630,6 +631,15 @@ function makeHookHandler(hooks, cwd, abortController) {
630
631
  function isModelFamilyListing(payload) {
631
632
  return typeof payload === "object" && payload !== null && Array.isArray(payload.families);
632
633
  }
634
+ function abortedBeforeSpawn() {
635
+ return {
636
+ stdin: { write() {}, end() {} },
637
+ stdout: async function* () {}(),
638
+ kill() {},
639
+ exited: Promise.resolve({ code: null, signal: "SIGTERM" }),
640
+ pid: null
641
+ };
642
+ }
633
643
  function query(args) {
634
644
  const { prompt, options } = args;
635
645
  if (options.canUseTool) {
@@ -689,6 +699,10 @@ function query(args) {
689
699
  ...options.toolSearchEnabled !== undefined ? { toolSearchEnabled: options.toolSearchEnabled } : {},
690
700
  ...options.insideSubagent !== undefined ? { insideSubagent: options.insideSubagent } : {},
691
701
  ...options.familyMetadata !== undefined ? { familyMetadata: options.familyMetadata } : {},
702
+ ...Array.isArray(options.tools) ? { tools: [...options.tools] } : {},
703
+ ...options.deferTools !== undefined ? { deferTools: [...options.deferTools] } : {},
704
+ ...options.legacyToolNames !== undefined ? { legacyToolNames: { ...options.legacyToolNames } } : {},
705
+ ...options.reservedMcpServerNames !== undefined ? { reservedMcpServerNames: [...options.reservedMcpServerNames] } : {},
692
706
  ...options.allowDangerouslySkipPermissions !== undefined ? { allowDangerouslySkipPermissions: options.allowDangerouslySkipPermissions } : {},
693
707
  ...runtimeHooksConfig !== undefined ? { hooks: runtimeHooksConfig } : {},
694
708
  ...options.includeHookEvents !== undefined ? { includeHookEvents: options.includeHookEvents } : {},
@@ -735,8 +749,33 @@ function query(args) {
735
749
  env: withTestKeychainRedirect(options.env) ?? process.env,
736
750
  ...options.abortController ? { signal: options.abortController.signal } : {}
737
751
  };
738
- const proc = (options.spawnClaudeCodeProcess ?? defaultSpawn)(spawnOptions);
752
+ const proc = options.abortController?.signal.aborted === true ? abortedBeforeSpawn() : (options.spawnClaudeCodeProcess ?? defaultSpawn)(spawnOptions);
739
753
  const maxBufferSize = options.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE;
754
+ let killRequested = false;
755
+ let spawnKillEscalation;
756
+ const killForAbort = () => {
757
+ if (killRequested)
758
+ return;
759
+ killRequested = true;
760
+ try {
761
+ proc.kill();
762
+ } catch {}
763
+ spawnKillEscalation = setTimeout(() => {
764
+ try {
765
+ proc.kill("SIGKILL");
766
+ } catch {}
767
+ }, KILL_GRACE_MS);
768
+ spawnKillEscalation.unref?.();
769
+ const clear = () => clearTimeout(spawnKillEscalation);
770
+ proc.exited.then(clear, clear);
771
+ };
772
+ let removeSpawnAbortListener = () => {};
773
+ if (options.abortController !== undefined && options.abortController.signal.aborted !== true) {
774
+ const signal = options.abortController.signal;
775
+ signal.addEventListener("abort", killForAbort, { once: true });
776
+ removeSpawnAbortListener = () => signal.removeEventListener("abort", killForAbort);
777
+ proc.exited.then(removeSpawnAbortListener, removeSpawnAbortListener);
778
+ }
740
779
  const pendingHostRequests = new Map;
741
780
  let generatorTerminated = false;
742
781
  function sendControlRequest(subtype, payload) {
@@ -877,9 +916,15 @@ function query(args) {
877
916
  if (aborted)
878
917
  return;
879
918
  aborted = true;
919
+ if (killRequested)
920
+ return;
921
+ killRequested = true;
880
922
  proc.kill();
881
923
  killTimer = setTimeout(() => proc.kill("SIGKILL"), KILL_GRACE_MS);
882
924
  killTimer.unref?.();
925
+ const timer = killTimer;
926
+ const clear = () => clearTimeout(timer);
927
+ proc.exited.then(clear, clear);
883
928
  };
884
929
  options.abortController?.signal.addEventListener("abort", onAbort);
885
930
  try {
@@ -979,8 +1024,7 @@ function query(args) {
979
1024
  } finally {
980
1025
  generatorTerminated = true;
981
1026
  options.abortController?.signal.removeEventListener("abort", onAbort);
982
- if (killTimer)
983
- clearTimeout(killTimer);
1027
+ removeSpawnAbortListener();
984
1028
  try {
985
1029
  proc.stdin.end();
986
1030
  } catch {}
@@ -1136,7 +1180,7 @@ function withTestKeychainRedirect(env) {
1136
1180
  return { ...env, [TEST_KEYCHAIN_ENV]: redirect };
1137
1181
  }
1138
1182
  // src/version.ts
1139
- var SDK_VERSION = "0.0.35";
1183
+ var SDK_VERSION = "0.0.38";
1140
1184
  // src/paths/project-key.ts
1141
1185
  var TRANSCRIPT_PROJECT_KEY_MAX_LENGTH = 64;
1142
1186
  var VENDOR_PROJECT_KEY_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
package/dist/options.d.ts CHANGED
@@ -155,6 +155,29 @@ export interface Options {
155
155
  familyMetadata?: {
156
156
  taskNative?: boolean;
157
157
  };
158
+ /**
159
+ * claude's own `tools` option: the BUILT-IN tool set, by name -- an array, or the `claude_code` preset
160
+ * (every built-in, the same as leaving it out). It is a VISIBILITY list, unlike `allowedTools` (which
161
+ * pre-approves): a built-in left out is not advertised, not searchable, and refused at dispatch as a
162
+ * tool the model was not offered. MCP servers' tools (including an in-process server's plain-named
163
+ * ones, `McpSdkServerConfig.toolNames`) are never filtered by it -- name only built-ins here. Leaving
164
+ * `ToolSearch` out switches deferral off, as in claude. Opt-in built-ins (`Search`) are advertised only
165
+ * when this list names them. See `RuntimeConfig.tools`.
166
+ */
167
+ tools?: string[] | {
168
+ type: "preset";
169
+ preset: "claude_code";
170
+ };
171
+ /**
172
+ * DISCLOSED WINTER option: tools that start DEFERRED (loaded through `ToolSearch` on first use) while
173
+ * Tool Search is active -- the host's way to defer a BUILT-IN (`CronList`), which otherwise never
174
+ * defers. See `RuntimeConfig.deferTools`.
175
+ */
176
+ deferTools?: string[];
177
+ /** DISCLOSED WINTER option: a tool's old names, `{ <old>: <current> }`. See `RuntimeConfig.legacyToolNames`. */
178
+ legacyToolNames?: Record<string, string>;
179
+ /** DISCLOSED WINTER option: MCP server names only the host's own in-process servers may use. See `RuntimeConfig.reservedMcpServerNames`. */
180
+ reservedMcpServerNames?: string[];
158
181
  allowDangerouslySkipPermissions?: boolean;
159
182
  canUseTool?: CanUseTool;
160
183
  hooks?: Partial<Record<HookEvent, HookCallbackMatcher[]>>;
@@ -279,6 +279,28 @@ export interface McpSdkServerConfig {
279
279
  name: string;
280
280
  timeout?: number;
281
281
  tools?: WireMcpToolDefinition[];
282
+ /**
283
+ * WINTER-OWNED EXTENSION: advertise some of this in-process server's tools under a HOST-CHOSEN plain
284
+ * name instead of `mcp__<server>__<tool>` -- `{ <tool as listTools() names it>: <plain name> }`.
285
+ *
286
+ * A renamed tool is an ordinary-looking tool to the model (`Browser`, never `mcp__…`) and that ONE name
287
+ * is its identity everywhere a name is carried: the provider request, the transcript, the host's frames,
288
+ * hook inputs' `tool_name`, `canUseTool`, `permission_denials`. Underneath it is still this server's MCP
289
+ * tool: it defers like one (unless `_meta["anthropic/alwaysLoad"]`), the call still arrives at the host
290
+ * as `sdk_mcp_call {server, tool}` with the ORIGINAL tool name, and `winter_mcp_server` / `canUseTool`'s
291
+ * `mcpServer` still name the server. The old `mcp__<server>__<tool>` spelling stays an EQUIVALENT
292
+ * identity: a permission rule (`mcp__<server>__<tool>`, `mcp__<server>__*`), a bare `disallowedTools`
293
+ * entry or a hook matcher written against it governs the renamed tool too (strictest-of, as for an
294
+ * alias) -- and, as for an alias, the call is then evaluated UNDER that spelling: the hook it selected
295
+ * and the prompt its ask rule raised carry `mcp__<server>__<tool>` as `tool_name`. A model call under the
296
+ * old spelling (a resumed history) runs as the renamed tool.
297
+ *
298
+ * A plain name must look like a tool name (`[A-Za-z][A-Za-z0-9_-]*`, at most 64 characters), must not
299
+ * start with `mcp__`, and must not collide with any other registered tool -- a collision refuses the
300
+ * session at startup rather than shadowing a tool. Only an in-process (`sdk`) server may rename; the
301
+ * host owns it.
302
+ */
303
+ toolNames?: Record<string, string>;
282
304
  }
283
305
  export type McpServerConfigForProcessTransport = McpStdioServerConfig | McpHttpServerConfig | McpSSEServerConfig | McpSdkServerConfig;
284
306
  export type AgentMcpServerSpec = string | Record<string, McpServerConfigForProcessTransport>;
@@ -389,6 +411,44 @@ export interface RuntimeConfig {
389
411
  familyMetadata?: {
390
412
  taskNative?: boolean;
391
413
  };
414
+ /**
415
+ * `Options.tools` (claude's own option): the BUILT-IN tool set this session is offered, by name.
416
+ * Absent = every built-in (claude's `claude_code` preset, which the wrapper serializes as absent).
417
+ * Filters built-ins only -- an MCP server's tools, a host's plain-named in-process tools and
418
+ * host-generated tools (`StructuredOutput`) are never named here and never filtered by it. A built-in
419
+ * left out is not advertised, not in ToolSearch's pool and refused at dispatch as a tool the model
420
+ * was not offered. `ToolSearch` left out switches deferral off (claude's rule: no search tool, no
421
+ * deferred tools). A few built-ins (`Search`) are OPT-IN: advertised only when this list names them.
422
+ */
423
+ tools?: string[];
424
+ /**
425
+ * Winter extension: tools that START DEFERRED -- loaded through `ToolSearch` on first use -- when Tool
426
+ * Search is active. A built-in otherwise never defers; an MCP tool already does (unless its
427
+ * `_meta["anthropic/alwaysLoad"]`, which still wins). `ToolSearch` itself is never deferred.
428
+ * Inert while deferral is inactive (full injection).
429
+ */
430
+ deferTools?: string[];
431
+ /**
432
+ * Winter extension: a tool's OLD names -- `{ <old name>: <current tool name> }` -- for a host whose
433
+ * own tool became another one (a host's in-process `mcp__<server>__Search` became the `Search`
434
+ * built-in). The old name keeps working everywhere a name is read: a model call under it (a resumed
435
+ * history taught it) runs the current tool, `ToolSearch`'s `select:` finds it, an agent definition's
436
+ * `tools` list keeps it, a rule, bare `disallowedTools` entry or hook matcher naming it governs the
437
+ * current tool (strictest-of, as for an alias). A plain-named in-process tool's own
438
+ * `mcp__<server>__<tool>` spelling needs no entry here: it is resolved the same way by itself. A key
439
+ * that names a registered tool is ignored (the live tool wins).
440
+ */
441
+ legacyToolNames?: Record<string, string>;
442
+ /**
443
+ * Winter extension: MCP server NAMES no server may take in this session except the host's own
444
+ * in-process (`type: "sdk"`) servers in `mcpServers` -- whatever its origin: a settings scope, a plugin's
445
+ * `.mcp.json`, an explicit non-sdk entry, the live `mcp_set_servers` door (each refused typed
446
+ * `reserved_name` and never connected), and an agent definition's inline server (connected under a
447
+ * renamed `<name>_<n>`, as for any clash; an inline in-process one under a reserved name is not
448
+ * connected). For a host whose own tools are classified by their server (trust keyed on
449
+ * `mcp__<server>__*`), so no foreign server can wear that spelling.
450
+ */
451
+ reservedMcpServerNames?: string[];
392
452
  mcpServers?: Record<string, McpServerConfigForProcessTransport>;
393
453
  strictMcpConfig?: boolean;
394
454
  toolAliases?: Record<string, string>;
@@ -306,6 +306,16 @@ export type WireContentBlock = {
306
306
  tool_use_id: string;
307
307
  content: string | WireContentBlock[];
308
308
  is_error?: boolean;
309
+ /**
310
+ * Winter-only, HOST-facing frame only (never model-visible, never in a transcript): the sites a web
311
+ * tool's result names and the icon the tool knows for each -- `WebFetch`'s page icon (the page's
312
+ * own declared `<link rel=icon>`, else its origin's `/favicon.ico`), `WebSearch`'s Exa `favicon`.
313
+ * At most 10 entries; every url https. Absent on every other result.
314
+ */
315
+ winter_site_icons?: Array<{
316
+ url: string;
317
+ icon_url: string;
318
+ }>;
309
319
  [k: string]: unknown;
310
320
  } | {
311
321
  type: "image";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-sdk",
3
- "version": "0.0.35",
3
+ "version": "0.0.38",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -45,15 +45,15 @@
45
45
  }
46
46
  },
47
47
  "dependencies": {
48
- "@yanlinglabs/winter-provider-catalog": "0.0.35"
48
+ "@yanlinglabs/winter-provider-catalog": "0.0.38"
49
49
  },
50
50
  "optionalDependencies": {
51
- "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.35"
51
+ "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.38"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^26.4.0",
55
- "@yanlinglabs/winter-conformance": "0.0.35",
56
- "@yanlinglabs/winter-agent-runtime": "0.0.35"
55
+ "@yanlinglabs/winter-agent-runtime": "0.0.38",
56
+ "@yanlinglabs/winter-conformance": "0.0.38"
57
57
  },
58
58
  "scripts": {}
59
59
  }