@yanlinglabs/winter-agent-sdk 0.0.17 → 0.0.20

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/index.d.ts CHANGED
@@ -27,7 +27,7 @@ export { SDK_VERSION } from "./version.js";
27
27
  export { MESSAGING_CONTROL_SUBTYPES, MESSAGING_CONTROL_SUBTYPE_LIST, MESSAGING_HOST_REQUEST_SUBTYPES, MESSAGING_RUNTIME_REQUEST_SUBTYPES, resolveFacetTarget } from "./protocol/messaging.js";
28
28
  export { isRuntimeAddress, isGlobalAgentMessage, isDeliveryOutcome, isListedRuntimeObjectArray, isPermissionClassLabel, isMessagingDeliverRequest, isMessagingChildRequest, isMessagingSubscribeIdleRequest, isMessagingReadNotificationsRequest, isMessagingNotificationsPage, isMessagingIdleNoticePayload, isNotificationRecord, } from "./protocol/messaging.js";
29
29
  export type { MessagingControlSubtype, MessagingDeliverRequest, MessagingChildRequest, MessagingSubscribeIdleRequest, MessagingSenderClassResponse, MessagingReadNotificationsRequest, MessagingNotificationsPage, MessagingIdleNoticePayload, } from "./protocol/messaging.js";
30
- export type { ProtocolVersion, WinterFrame, SdkMessage as ProtocolSdkMessage, InitFrame, UserFrame, DataFrame, ControlRequestFrame, ControlResponseFrame, UnknownFrame, SDKHookStartedMessage, SDKHookProgressMessage, SDKHookResponseMessage, SDKPermissionDeniedMessage, SDKPermissionDenial, SDKTaskStartedMessage, SDKTaskNotificationMessage, SDKTaskUpdatedMessage, SDKTaskProgressMessage, SDKBackgroundTasksChangedMessage, SDKLocalCommandOutputMessage, BackgroundTaskMessage, SDKCompactBoundaryMessage, SDKSessionStateChangedMessage, WireContentBlock, WireStreamEvent, WireStreamEventDelta, SDKPartialAssistantMessage, SDKAssistantMessageError, SDKAPIRetryMessage, SDKRateLimitEvent, SDKRateLimitInfo, SDKAuthStatusMessage, SDKThinkingTokensMessage, SDKModelRefusalFallbackMessage, SDKModelRefusalNoFallbackMessage, SDKReasoningSummaryMessage, SDKModelSwitchMessage, SDKContinuityWarningMessage, } from "./protocol/frames.js";
30
+ export type { ProtocolVersion, WinterFrame, SdkMessage as ProtocolSdkMessage, InitFrame, UserFrame, DataFrame, ControlRequestFrame, ControlResponseFrame, ControlCancelRequestFrame, WireResultUsage, UnknownFrame, SDKHookStartedMessage, SDKHookProgressMessage, SDKHookResponseMessage, SDKPermissionDeniedMessage, SDKPermissionDenial, SDKTaskStartedMessage, SDKTaskNotificationMessage, SDKTaskUpdatedMessage, SDKTaskProgressMessage, SDKBackgroundTasksChangedMessage, SDKLocalCommandOutputMessage, BackgroundTaskMessage, SDKCompactBoundaryMessage, SDKSessionStateChangedMessage, WireContentBlock, WireStreamEvent, WireStreamEventDelta, SDKPartialAssistantMessage, SDKAssistantMessageError, SDKAPIRetryMessage, SDKRateLimitEvent, SDKRateLimitInfo, SDKAuthStatusMessage, SDKThinkingTokensMessage, SDKModelRefusalFallbackMessage, SDKModelRefusalNoFallbackMessage, SDKReasoningSummaryMessage, SDKModelSwitchMessage, SDKContinuityWarningMessage, } from "./protocol/frames.js";
31
31
  export { resolveWinterHome, resolveKeychainServiceForProfile, isUnset } from "./paths/home.js";
32
32
  export { transcriptProjectKey, TRANSCRIPT_PROJECT_KEY_MAX_LENGTH, isVendorCompliantProjectKey } from "./paths/project-key.js";
33
33
  export { compatibilityKeys } from "./paths/keys.js";
package/dist/index.js CHANGED
@@ -666,6 +666,7 @@ function query(args) {
666
666
  proc.stdin.write(encodeFrame(frame));
667
667
  }
668
668
  const controlRequestHandlers = new Map;
669
+ const incomingRequestAborts = new Map;
669
670
  async function handleIncomingControlRequest(cf) {
670
671
  try {
671
672
  const handler = controlRequestHandlers.get(cf.subtype);
@@ -678,8 +679,12 @@ function query(args) {
678
679
  });
679
680
  return;
680
681
  }
682
+ const cancel = new AbortController;
683
+ incomingRequestAborts.set(cf.requestId, cancel);
681
684
  try {
682
- const result = await handler(cf.payload);
685
+ const result = await handler(cf.payload, { signal: cancel.signal });
686
+ if (cancel.signal.aborted)
687
+ return;
683
688
  if (result.ok) {
684
689
  writeControlResponse({
685
690
  type: "control_response",
@@ -691,19 +696,27 @@ function query(args) {
691
696
  writeControlResponse({ type: "control_response", requestId: cf.requestId, ok: false, error: result.error });
692
697
  }
693
698
  } catch (err) {
699
+ if (cancel.signal.aborted)
700
+ return;
694
701
  const message = err instanceof Error ? err.message : String(err);
695
702
  writeControlResponse({ type: "control_response", requestId: cf.requestId, ok: false, error: { code: "handler_threw", message } });
703
+ } finally {
704
+ incomingRequestAborts.delete(cf.requestId);
696
705
  }
697
706
  } catch {}
698
707
  }
699
708
  function makePermissionHandler(canUseTool) {
700
- return async (payload) => {
709
+ return async (payload, handlerCtx) => {
701
710
  const req = payload;
702
711
  const controller = new AbortController;
703
712
  if (options.abortController?.signal.aborted)
704
713
  controller.abort();
705
714
  else
706
715
  options.abortController?.signal.addEventListener("abort", () => controller.abort(), { once: true });
716
+ if (handlerCtx?.signal.aborted === true)
717
+ controller.abort();
718
+ else
719
+ handlerCtx?.signal.addEventListener("abort", () => controller.abort(), { once: true });
707
720
  let result;
708
721
  try {
709
722
  result = await canUseTool(req.toolName, req.input, {
@@ -828,6 +841,13 @@ function query(args) {
828
841
  }
829
842
  continue;
830
843
  }
844
+ if (frame.type === "control_cancel_request") {
845
+ const cancelledId = frame.requestId ?? "";
846
+ const cancelled = incomingRequestAborts.get(cancelledId);
847
+ incomingRequestAborts.delete(cancelledId);
848
+ cancelled?.abort();
849
+ continue;
850
+ }
831
851
  if (frame.type === "control_request") {
832
852
  handleIncomingControlRequest(frame);
833
853
  continue;
@@ -1001,7 +1021,7 @@ function query(args) {
1001
1021
  return gen;
1002
1022
  }
1003
1023
  // src/version.ts
1004
- var SDK_VERSION = "0.0.17";
1024
+ var SDK_VERSION = "0.0.20";
1005
1025
  // src/paths/project-key.ts
1006
1026
  var TRANSCRIPT_PROJECT_KEY_MAX_LENGTH = 64;
1007
1027
  var VENDOR_PROJECT_KEY_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
@@ -1044,13 +1064,41 @@ function resolveCanonical(raw) {
1044
1064
  }
1045
1065
  }
1046
1066
  function gitCommonRoot(resolvedCwd) {
1067
+ let commonDir;
1068
+ try {
1069
+ const raw = runGit(["-C", resolvedCwd, "rev-parse", "--git-common-dir"]);
1070
+ commonDir = platformNormalize(realpathSync(isAbsolute(raw) ? raw : resolvePath(resolvedCwd, raw)));
1071
+ } catch {
1072
+ return null;
1073
+ }
1074
+ const candidate = dirname2(commonDir);
1075
+ if (isWithin(resolvedCwd, candidate))
1076
+ return candidate;
1077
+ if (registeredWorktrees(commonDir).some((worktree) => isWithin(resolvedCwd, worktree)))
1078
+ return candidate;
1079
+ const configured = configuredWorktree(commonDir);
1080
+ if (configured !== null && isWithin(resolvedCwd, configured))
1081
+ return configured;
1082
+ return null;
1083
+ }
1084
+ function runGit(args) {
1085
+ return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
1086
+ }
1087
+ function isWithin(path, dir) {
1088
+ return path === dir || path.startsWith(dir.endsWith("/") ? dir : `${dir}/`);
1089
+ }
1090
+ function registeredWorktrees(commonDir) {
1091
+ try {
1092
+ return runGit(["--git-dir", commonDir, "worktree", "list", "--porcelain"]).split(`
1093
+ `).filter((line) => line.startsWith("worktree ")).map((line) => resolveCanonical(line.slice("worktree ".length)));
1094
+ } catch {
1095
+ return [];
1096
+ }
1097
+ }
1098
+ function configuredWorktree(commonDir) {
1047
1099
  try {
1048
- const raw = execFileSync("git", ["-C", resolvedCwd, "rev-parse", "--git-common-dir"], {
1049
- encoding: "utf8",
1050
- stdio: ["ignore", "pipe", "ignore"]
1051
- }).trim();
1052
- const commonDir = isAbsolute(raw) ? raw : resolvePath(resolvedCwd, raw);
1053
- return platformNormalize(dirname2(realpathSync(commonDir)));
1100
+ const raw = runGit(["--git-dir", commonDir, "config", "--get", "core.worktree"]);
1101
+ return raw.length > 0 ? resolveCanonical(isAbsolute(raw) ? raw : resolvePath(commonDir, raw)) : null;
1054
1102
  } catch {
1055
1103
  return null;
1056
1104
  }
package/dist/options.d.ts CHANGED
@@ -275,7 +275,9 @@ export interface Options {
275
275
  autoClassifier?: AutoClassifierConfig;
276
276
  /**
277
277
  * DISCLOSED WINTER option (P2 carry, wired in T10): the advisor/reviewer backend's model, same
278
- * selection path. Its optional `authRef` (fix wave, Ruling E-1) is the advisor's OWN credential:
278
+ * selection path -- a provider-qualified tag (`<providerId>/<model>`) names its own provider, so a
279
+ * reviewer on another provider than the session's is just that tag plus that provider's credential
280
+ * (the `web.fetch.digestModel` shape). Its optional `authRef` (fix wave, Ruling E-1) is the advisor's OWN credential:
279
281
  * a target on another provider than the session's never inherits the session's -- it uses the
280
282
  * route's ref, else the target provider's own keychain record (`<providerId>:default`), else a
281
283
  * typed `no-credential-for-provider` refusal at its first generation.
@@ -473,6 +473,18 @@ export interface AutoClassifierConfig {
473
473
  * advisor's provider, the advisor's provider refuses with a typed `no-credential-for-provider`.
474
474
  */
475
475
  export interface AdvisorConfig {
476
+ /**
477
+ * The reviewer's model: a PROVIDER-QUALIFIED TAG (`<providerId>/<model>`, e.g.
478
+ * `codex-oauth/gpt-5.6-sol`) or a slot name, resolved through the SAME selection path as the
479
+ * session model -- exactly as `WebFetchConfig.digestModel` is.
480
+ *
481
+ * THE TAG IS THE PROVIDER IDENTITY; there is deliberately no second field for it. A qualified tag
482
+ * resolves under ITS OWN provider, never under the session's (`Options.provider`), so an advisor on
483
+ * another provider than the session's needs nothing but that tag and, in `authRef`, that provider's
484
+ * own credential. A host that splits the tag down to a bare model id before passing it has thrown
485
+ * the provider away: a bare id is read as a slot name or canonical id and resolved by the session's
486
+ * slot rules, which is a different question.
487
+ */
476
488
  model: string;
477
489
  authRef?: CredentialRef;
478
490
  }
@@ -42,11 +42,56 @@ export interface ControlResponseFrame {
42
42
  message: string;
43
43
  };
44
44
  }
45
+ /**
46
+ * Runtime -> host: "I stopped waiting for control_request `requestId`" (lane C, C2 -- claude parity:
47
+ * claude's structuredIO writes `control_cancel_request` when a pending request's signal aborts, e.g.
48
+ * a turn interrupted while a permission prompt is open). The host aborts the callback it is running
49
+ * for that request (`canUseTool`'s `signal`) and owes no response; one sent anyway is dropped.
50
+ */
51
+ /**
52
+ * `result.usage`: claude's `NonNullableUsage` (every `BetaUsage` field, none null), carrying THIS
53
+ * turn's main-loop generations -- claude builds a fresh QueryEngine per prompt in SDK mode, so its
54
+ * `totalUsage` starts at EMPTY_USAGE each turn. The four token counts are real; the rest is filled the
55
+ * way the pinned binary's own EMPTY_USAGE fills a value it has no data for, and says so:
56
+ * - `cache_creation`: every cache write Winter marks is the default 5-minute ephemeral one (the
57
+ * adapters send `{type: "ephemeral"}` with no ttl), so `ephemeral_5m_input_tokens` equals
58
+ * `cache_creation_input_tokens` and `ephemeral_1h_input_tokens` is 0;
59
+ * - `server_tool_use`: Winter's WebSearch/WebFetch run client-side, never as billed server tools: 0/0;
60
+ * - `output_tokens_details.thinking_tokens`: 0 -- providers' reasoning-token counts are not threaded
61
+ * through Winter's usage yet (they are included in `output_tokens`);
62
+ * - `service_tier: "standard"`, `inference_geo: ""`, `iterations: []`, `speed: "standard"`: the
63
+ * pinned EMPTY_USAGE's own values.
64
+ */
65
+ export interface WireResultUsage {
66
+ output_tokens_details: {
67
+ thinking_tokens: number;
68
+ };
69
+ input_tokens: number;
70
+ cache_creation_input_tokens: number;
71
+ cache_read_input_tokens: number;
72
+ output_tokens: number;
73
+ server_tool_use: {
74
+ web_search_requests: number;
75
+ web_fetch_requests: number;
76
+ };
77
+ service_tier: "standard" | "priority" | "batch";
78
+ cache_creation: {
79
+ ephemeral_1h_input_tokens: number;
80
+ ephemeral_5m_input_tokens: number;
81
+ };
82
+ inference_geo: string;
83
+ iterations: unknown[];
84
+ speed: "standard" | "fast";
85
+ }
86
+ export interface ControlCancelRequestFrame {
87
+ type: "control_cancel_request";
88
+ requestId: string;
89
+ }
45
90
  export interface UnknownFrame {
46
91
  type: string;
47
92
  [k: string]: unknown;
48
93
  }
49
- export type WinterFrame = InitFrame | UserFrame | DataFrame | ControlRequestFrame | ControlResponseFrame | UnknownFrame;
94
+ export type WinterFrame = InitFrame | UserFrame | DataFrame | ControlRequestFrame | ControlResponseFrame | ControlCancelRequestFrame | UnknownFrame;
50
95
  export interface SDKHookStartedMessage {
51
96
  type: "system";
52
97
  subtype: "hook_started";
@@ -618,6 +663,8 @@ export type SdkMessage = {
618
663
  structured_output?: unknown;
619
664
  terminal_reason?: "structured_output_retry_exhausted" | "api_error" | string;
620
665
  api_error_status?: number | null;
666
+ /** THIS turn's main-loop usage -- claude's `result.usage` (dist-session fixes C1); see `WireResultUsage`. */
667
+ usage?: WireResultUsage;
621
668
  permission_denials: SDKPermissionDenial[];
622
669
  [k: string]: unknown;
623
670
  } | {
package/dist/query.d.ts CHANGED
@@ -21,7 +21,14 @@ export type ControlRequestHandlerResult = {
21
21
  message: string;
22
22
  };
23
23
  };
24
- export type ControlRequestHandler = (payload: unknown) => Promise<ControlRequestHandlerResult>;
24
+ /**
25
+ * A handler for one runtime-originated control_request subtype. `ctx.signal` aborts when the runtime
26
+ * CANCELS the request (`control_cancel_request` -- lane C, C2, claude parity): the handler should stop
27
+ * waiting, and whatever it returns afterwards is not sent (the runtime already stopped waiting).
28
+ */
29
+ export type ControlRequestHandler = (payload: unknown, ctx?: {
30
+ signal: AbortSignal;
31
+ }) => Promise<ControlRequestHandlerResult>;
25
32
  export interface QueryInternal {
26
33
  registerControlRequestHandler(subtype: string, handler: ControlRequestHandler): void;
27
34
  respondPermission(requestId: string, result: PermissionResult): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yanlinglabs/winter-agent-sdk",
3
- "version": "0.0.17",
3
+ "version": "0.0.20",
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.17"
48
+ "@yanlinglabs/winter-provider-catalog": "0.0.20"
49
49
  },
50
50
  "optionalDependencies": {
51
- "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.17"
51
+ "@yanlinglabs/winter-agent-sdk-darwin-arm64": "0.0.20"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^26.4.0",
55
- "winter-agent-runtime": "0.0.17",
56
- "@yanlinglabs/winter-conformance": "0.0.17"
55
+ "@yanlinglabs/winter-conformance": "0.0.20",
56
+ "winter-agent-runtime": "0.0.20"
57
57
  },
58
58
  "scripts": {}
59
59
  }