@yaklabs-ai/plugin-sdk 0.11.0 → 0.12.3

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,4 +1,3 @@
1
- import type { TypeSafeClient } from '@typesafe-ai/sdk';
2
1
  import { z } from 'zod';
3
2
 
4
3
  /**
@@ -23,6 +22,9 @@ declare interface AccountIdentity {
23
22
  readonly avatarUrl?: string;
24
23
  /** Issuer-asserted beta-tester flag; only Kay's product-account service sets it. */
25
24
  readonly betaTester?: boolean;
25
+ readonly activeOrganizationId?: string;
26
+ readonly organizationSelectionState?: "selected" | "unresolved";
27
+ readonly organizationScopeEpoch?: number;
26
28
  /**
27
29
  * Provider-native session id claim (WorkOS's `sid`), needed to call the provider's own
28
30
  * session-revocation endpoint at sign-out (`POST /user_management/sessions/revoke`, not yet
@@ -54,8 +56,18 @@ declare interface AgentToolResult<T = JsonValue_3 | undefined> {
54
56
  content: (TextContent | ImageContent)[];
55
57
  /** Arbitrary structured details for logs or UI rendering. */
56
58
  details: T;
59
+ /**
60
+ * Machine-readable result matching the tool's `outputSchema`, for programmatic callers. Not sent
61
+ * to the model; `content` remains the model-facing result.
62
+ */
63
+ structuredContent?: JsonValue_3;
57
64
  /** Usage from the final tool execution itself, if available. Not used for main LLM context accounting. */
58
65
  usage?: Usage;
66
+ /**
67
+ * Report a failure without throwing. The model sees `content` as an error result, like a thrown
68
+ * error, but `details` and `structuredContent` are kept for the UI and programmatic callers.
69
+ */
70
+ isError?: boolean;
59
71
  /**
60
72
  * Hint that the agent should stop after the current tool batch.
61
73
  * Early termination only happens when every finalized tool result in the batch sets this to true.
@@ -291,6 +303,20 @@ declare interface BackgroundTaskSnapshot {
291
303
 
292
304
  declare type BackgroundTaskStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
293
305
 
306
+ /** Fields shared by every catalog entry, regardless of what you can do with it. */
307
+ declare interface BaseModel<TApi extends string> {
308
+ id: string;
309
+ name: string;
310
+ api: TApi;
311
+ provider: ProviderId;
312
+ baseUrl: string;
313
+ input: ("text" | "image")[];
314
+ /** Provider input limits and cache-safe preprocessing metadata. */
315
+ inputLimits?: ModelInputLimits;
316
+ cost: ModelCost;
317
+ headers?: Record<string, string>;
318
+ }
319
+
294
320
  /** Compatibility settings for Amazon Bedrock models. */
295
321
  declare interface BedrockCompat {
296
322
  /** Whether the model supports Bedrock strict tool schemas. Default: false. */
@@ -354,7 +380,7 @@ export declare function collectAuthDeclarationsFromKayAgentActivate(activate: Ka
354
380
  /**
355
381
  * Runs `activate` once against a fake, capture-only {@link KayAgentPluginApi} and returns
356
382
  * every `UriExtensionModule` it registered, in registration order. Every non-`uri.registerModule`
357
- * call (`registerTool`, `registerSearchProvider`, `registerConverter`, `auth.*`) is a silent
383
+ * call (`registerTool`, `registerSearchProvider`, `registerInteractionDriver`, `registerConverter`, `auth.*`) is a silent
358
384
  * no-op: those surfaces are irrelevant to disclosure, and a plugin that also calls them must not
359
385
  * throw or leak a side effect from this probe.
360
386
  */
@@ -486,6 +512,13 @@ declare interface DelegatedConnectContext {
486
512
  /** What a delegated scheme's `connect` resolves with: the material to vault + optional label/provenance. */
487
513
  declare interface DelegatedConnectResult {
488
514
  readonly material: DelegatedSecretMaterial;
515
+ /**
516
+ * A stable account id for this sign-in, used instead of a freshly minted one, so a reconnect
517
+ * overwrites the same record (and keeps it the default) rather than adding a sibling. 1-128
518
+ * characters: letters, digits, and `._:@+-`, starting with a letter or digit; any other value
519
+ * fails the connect. Absent mints a fresh id per connect.
520
+ */
521
+ readonly accountId?: string;
489
522
  /** Account display label; a caller-supplied `service_connect` label takes precedence. */
490
523
  readonly label?: string;
491
524
  /** Structured provenance the scheme derived during connect; persisted via the vault's write-path. */
@@ -628,6 +661,328 @@ declare interface ImageContent {
628
661
  mimeType: string;
629
662
  }
630
663
 
664
+ /**
665
+ * The value a Browser Interaction driver sets as `credentialGuard` to declare that every act runs
666
+ * the page-side credential guard from `@kay/interaction-page-scripts` (never type into or click a
667
+ * sign-in form). The engine refuses to register a browser driver without it.
668
+ */
669
+ declare const INTERACTION_CREDENTIAL_GUARD = "kay.credential-guard/v1";
670
+
671
+ declare interface InteractionActed {
672
+ /** The address of the target acted on, when it changed (an open landed in a new page). */
673
+ readonly target?: string;
674
+ readonly url: string;
675
+ readonly title: string;
676
+ /** What the action visibly caused: a dialog, an alert, a page opened beside it. */
677
+ readonly changes?: readonly string[];
678
+ readonly notes?: readonly string[];
679
+ /** Fresh browser sign-in state for the service the engine named in the open spec. */
680
+ readonly webSession?: {
681
+ readonly state: "signed-in" | "signed-out" | "unknown";
682
+ readonly checkedAt: number | null;
683
+ };
684
+ }
685
+
686
+ /**
687
+ * One action over a candidate from the latest observation of the same target. `fill` replaces the
688
+ * field's contents unless `append` is set, and presses Enter afterwards when `submit` is set.
689
+ */
690
+ declare type InteractionAction = {
691
+ readonly kind: "click";
692
+ readonly element: InteractionCandidate;
693
+ } | {
694
+ readonly kind: "fill";
695
+ readonly element: InteractionCandidate;
696
+ readonly text: string;
697
+ readonly submit?: boolean;
698
+ readonly append?: boolean;
699
+ } | {
700
+ readonly kind: "select";
701
+ readonly element: InteractionCandidate;
702
+ } | {
703
+ readonly kind: "activate";
704
+ readonly element: InteractionCandidate;
705
+ } | {
706
+ readonly kind: "press";
707
+ readonly element: InteractionCandidate;
708
+ }
709
+ /**
710
+ * Scroll by `delta` px (positive is down). With `element` (a `scroll` candidate that names a
711
+ * node), the scroll container that candidate scrolls; without, the whole page or window.
712
+ */
713
+ | {
714
+ readonly kind: "scroll";
715
+ readonly delta: number;
716
+ readonly element?: InteractionCandidate;
717
+ } | {
718
+ readonly kind: "wait";
719
+ };
720
+
721
+ /**
722
+ * One element (or control) an observation offers, with the kind of action it supports. `id` is
723
+ * the driver's per-observation id; the engine mints its own session-unique ids over it. Extra
724
+ * fields are the driver's native identity (a page node number, an accessibility token), returned
725
+ * to the driver untouched in {@link InteractionAction}.
726
+ */
727
+ declare interface InteractionCandidate {
728
+ readonly id: string;
729
+ readonly kind: InteractionCandidateKind;
730
+ readonly label: string;
731
+ readonly node?: number | undefined;
732
+ readonly role?: string | undefined;
733
+ readonly value?: string | undefined;
734
+ readonly current_value?: string | undefined;
735
+ readonly checked?: string | undefined;
736
+ readonly selected?: string | undefined;
737
+ readonly expanded?: string | undefined;
738
+ readonly delta?: number | undefined;
739
+ /** Where an element outside the visible area sits; absent means on screen. */
740
+ readonly position?: "above" | "below" | "beside" | undefined;
741
+ /** A `press` candidate's key (`return`, `escape`, `tab`); its id is `press_<key>` by convention. */
742
+ readonly key?: string | undefined;
743
+ /** The region the element sits in; see {@link InteractionRegion}. */
744
+ readonly region?: InteractionRegion | undefined;
745
+ /**
746
+ * The `node` of the scroll container (a list or pane that scrolls on its own) the element sits
747
+ * in, matching that container's `scroll` candidates; absent when it sits in none.
748
+ */
749
+ readonly scroller?: number | undefined;
750
+ readonly [native: string]: unknown;
751
+ }
752
+
753
+ /** The candidate kinds a driver may offer. */
754
+ declare type InteractionCandidateKind = "click" | "fill" | "select" | "scroll" | "wait" | "activate" | "press";
755
+
756
+ declare interface InteractionDriver {
757
+ /** Unique in the session. `builtin:` is reserved for Kay's own drivers. */
758
+ readonly id: string;
759
+ /** Human-readable name the agent sees beside the driver's targets. */
760
+ readonly label: string;
761
+ readonly kind: InteractionDriverKind;
762
+ /** Required on a `browser` driver; see {@link INTERACTION_CREDENTIAL_GUARD}. */
763
+ readonly credentialGuard?: typeof INTERACTION_CREDENTIAL_GUARD;
764
+ /** Whether the driver can act at all right now (platform, grants, a live connection). */
765
+ isAvailable(): Promise<boolean>;
766
+ targets(): Promise<InteractionOutcome<{
767
+ readonly targets: readonly InteractionTarget[];
768
+ }>>;
769
+ observe(target: string | undefined, options?: {
770
+ readonly fullText?: boolean;
771
+ }): Promise<InteractionOutcome<InteractionObserved>>;
772
+ act(target: string | undefined, action: InteractionAction): Promise<InteractionOutcome<InteractionActed>>;
773
+ screenshot(target: string | undefined): Promise<InteractionOutcome<{
774
+ readonly png: Uint8Array;
775
+ }>>;
776
+ open(spec: InteractionOpenSpec): Promise<InteractionOutcome<InteractionActed>>;
777
+ }
778
+
779
+ /**
780
+ * The Interaction driver contract: what a plugin implements to let Kay's agent drive something
781
+ * on the user's machine (a browser, an app, a remote screen) through the harness-owned
782
+ * Interaction engine.
783
+ *
784
+ * The engine owns everything that is policy: the `interaction_*` tools, the session-unique element
785
+ * ids the agent sees, the run loop that drives a goal step by step, the permission check before
786
+ * every action, screenshots, and reports. A driver owns only its own I/O: list what it can drive,
787
+ * observe one target as a list of actionable candidates, perform one action on a candidate from
788
+ * its latest observation, capture a screenshot, and open a page or app. It chooses no actions,
789
+ * maps no permissions, and retries nothing beyond its own transport. That split is what makes
790
+ * every tool work on every driver.
791
+ *
792
+ * Shape mirrors `SearchProvider` behind `web_search`: one tool set owned by the harness, many
793
+ * backends registered per session, the `builtin:` id namespace reserved for Kay's own.
794
+ */
795
+ /** "Browser Interaction" drives web pages; "Computer Use Interaction" drives native apps. */
796
+ declare type InteractionDriverKind = "browser" | "computer-use";
797
+
798
+ /** A failed operation. `unavailable`: nothing to act on; `refused`: will not run as asked. */
799
+ declare interface InteractionFailure {
800
+ readonly ok: false;
801
+ readonly reason: "unavailable" | "refused" | "error";
802
+ readonly message: string;
803
+ }
804
+
805
+ /** What a driver sees on one target. */
806
+ declare interface InteractionObservation {
807
+ readonly url: string;
808
+ readonly title: string;
809
+ /** Visible text, or the whole page's text when the observe asked for `fullText`; capped. */
810
+ readonly text: string;
811
+ readonly actions: readonly InteractionCandidate[];
812
+ /** Candidates the driver dropped to stay within its cap. */
813
+ readonly omitted_actions: number;
814
+ }
815
+
816
+ declare interface InteractionObserved {
817
+ /** The address of the target observed; absent when the default target has no address. */
818
+ readonly target?: string;
819
+ readonly observation: InteractionObservation;
820
+ /** Lines the engine shows the agent under the observation (where the target sits, siblings). */
821
+ readonly notes?: readonly string[];
822
+ }
823
+
824
+ /** Navigate a page to `url`, or launch / switch to the app `app` names (a bundle id or a name). */
825
+ declare interface InteractionOpenSpec {
826
+ readonly target?: string;
827
+ readonly url?: string;
828
+ readonly app?: string;
829
+ /** The service whose browser sign-in state to report after navigation, when the engine knows it. */
830
+ readonly webSessionServiceId?: string;
831
+ }
832
+
833
+ declare type InteractionOutcome<T> = ({
834
+ readonly ok: true;
835
+ } & T) | InteractionFailure;
836
+
837
+ /**
838
+ * The part of a page or window an element sits in: the open dialog, menu, or sheet it belongs to,
839
+ * else its landmark (navigation, main, a window). `front` marks the overlay covering the rest: a
840
+ * modal dialog (or one held by a full-screen overlay), a sheet, or a floating menu, the last such
841
+ * in the document; elements outside it
842
+ * usually cannot be clicked while it is open. A docked or always-visible dialog or menu is a
843
+ * region but never in front.
844
+ */
845
+ declare interface InteractionRegion {
846
+ readonly role: string;
847
+ readonly name: string;
848
+ readonly front?: boolean | undefined;
849
+ }
850
+
851
+ /**
852
+ * One surface Kay ships, in the driver contract's own shape, so a plugin's driver is the surface
853
+ * plus an identity: `{ id, label, kind, ...api.interaction.desktop }`. Each call is checked
854
+ * against the plugin's `interaction.surface {surface}` grant, and then against the session's
855
+ * permissions exactly as the agent's `interaction_*` tools are: an act is judged as the plugin
856
+ * permission it amounts to (a Send click in Gmail as a Gmail write), an open as `interaction_open`,
857
+ * an observation or screenshot as `interaction_read`. A refused or unavailable call answers
858
+ * `{ ok: false }` (and `isAvailable()` false) rather than throwing.
859
+ */
860
+ declare type InteractionSurface = Pick<InteractionDriver, "isAvailable" | "targets" | "observe" | "act" | "screenshot" | "open">;
861
+
862
+ /**
863
+ * One thing a driver can be pointed at. `id` is a driver-owned address, unique across every
864
+ * driver in the session (prefix it with your scheme), that the agent passes back as `target`.
865
+ * `active` marks the driver's default target.
866
+ */
867
+ declare interface InteractionTarget {
868
+ readonly id: string;
869
+ readonly title: string;
870
+ readonly url?: string;
871
+ readonly active: boolean;
872
+ }
873
+
874
+ /** The answer shape one question gets back. */
875
+ declare type JevAnswerFor<Q> = Q extends JevBoolQuestion ? JevBoolAnswer : Q extends JevChoiceQuestion<infer L> ? JevChoiceAnswer<L> : Q extends JevScoreQuestion ? JevScoreAnswer : never;
876
+
877
+ declare interface JevBoolAnswer {
878
+ readonly type: "bool";
879
+ /** P(yes), from 0 to 1. */
880
+ readonly probability: number;
881
+ }
882
+
883
+ /** A yes/no question. `criteria` says what yes and what no mean; omit it to leave both open. */
884
+ declare interface JevBoolQuestion {
885
+ readonly type: "bool";
886
+ readonly instructions: string;
887
+ readonly criteria?: {
888
+ readonly true: string;
889
+ readonly false: string;
890
+ };
891
+ }
892
+
893
+ declare interface JevChoiceAnswer<L extends string = string> {
894
+ readonly type: "choice";
895
+ readonly choice: L;
896
+ readonly confidence: number;
897
+ /** P(label) for every offered label. */
898
+ readonly probabilities: Readonly<Record<L, number>>;
899
+ }
900
+
901
+ /** A choice among labels. Each label maps to what it means; an empty string leaves it open. */
902
+ declare interface JevChoiceQuestion<L extends string = string> {
903
+ readonly type: "choice";
904
+ readonly instructions: string;
905
+ readonly criteria: Readonly<Record<L, string>>;
906
+ }
907
+
908
+ /** One answered request: every asked question has an answer of its own kind. */
909
+ declare interface JevClassification<Q extends JevQuestions = JevQuestions> {
910
+ /** The Jev model the host asked. */
911
+ readonly model: string;
912
+ readonly answers: {
913
+ readonly [K in keyof Q]: JevAnswerFor<Q[K]>;
914
+ };
915
+ /** Token counts, when the route reported them. */
916
+ readonly usage?: {
917
+ readonly inputTokens: number;
918
+ readonly outputTokens: number;
919
+ };
920
+ }
921
+
922
+ declare interface JevClassifyRequest<Q extends JevQuestions = JevQuestions> {
923
+ readonly state: JevState;
924
+ readonly questions: Q;
925
+ }
926
+
927
+ /**
928
+ * The Jev client a `use` body receives. `classify` either resolves with an answer for every
929
+ * asked question, each of its question's kind, or rejects with a {@link JevCallError}; it never
930
+ * resolves with a partial or substituted answer.
931
+ */
932
+ declare interface JevClient {
933
+ classify<const Q extends JevQuestions>(request: JevClassifyRequest<Q>, options?: {
934
+ readonly signal?: AbortSignal;
935
+ }): Promise<JevClassification<Q>>;
936
+ }
937
+
938
+ declare type JevQuestion = JevBoolQuestion | JevChoiceQuestion | JevScoreQuestion;
939
+
940
+ /** A rubric: questions keyed by the name each answer comes back under. */
941
+ declare type JevQuestions = Readonly<Record<string, JevQuestion>>;
942
+
943
+ declare interface JevScoreAnswer {
944
+ readonly type: "score";
945
+ /** The expected score, from 0 to the number of rungs minus one. */
946
+ readonly score: number;
947
+ readonly confidence: number;
948
+ }
949
+
950
+ /** A score over ordered rungs: the first criterion scores 0, the next 1, and so on. */
951
+ declare interface JevScoreQuestion {
952
+ readonly type: "score";
953
+ readonly instructions: string;
954
+ readonly criteria: readonly string[];
955
+ }
956
+
957
+ /**
958
+ * ---
959
+ * responsibility: The plugin-facing CONTRACT for Jev typed-decision inference — the shape of
960
+ * `api.withJev` on both plugin SDK targets, the {@link JevClient} a `use` body receives, the
961
+ * question and answer vocabulary it speaks, and the fallback-only implementation a host
962
+ * without a Jev lane binds.
963
+ * owns:
964
+ * - {@link WithJev}, {@link WithJevOptions}, {@link createFallbackOnlyWithJev}.
965
+ * - {@link JevClient}, the {@link JevQuestion} and {@link JevAnswer} vocabulary, and
966
+ * {@link JevCallError}.
967
+ * does_not_own:
968
+ * - The wire. The host serves `classify` over whichever route the user's Jev provider order
969
+ * picks; a plugin never sees a vendor client, a vendor type, or a route.
970
+ * - Credential resolution, the `jev.use` decision, the Jev-branch budget, or usage accounting.
971
+ * Those are host-side (`@kay/agent-runtime`'s `kay/jev/`).
972
+ * ---
973
+ *
974
+ * The vocabulary is Kay's own (SDK 0.12.0). Before it, `use` received TypeSafe's `TypeSafeClient`
975
+ * and plugins coded against a vendor's types. It follows pi's classifier contract, which the host
976
+ * runs Jev through: a yes/no question is `bool` and answers a `probability`, every instruction and
977
+ * criterion is text, and the state is a JSON object.
978
+ */
979
+ /**
980
+ * The state a rubric reads: a JSON object, never text or an array. Every value must survive
981
+ * `JSON.stringify` unchanged (no functions or class instances), because it is sent as JSON.
982
+ * Typed as `object` so a state declared as an interface fits without an index signature.
983
+ */
984
+ declare type JevState = object;
985
+
631
986
  declare type JsonObject = {
632
987
  [key: string]: JsonValue_3;
633
988
  };
@@ -668,10 +1023,18 @@ declare interface KayAgentEvents {
668
1023
  publish(localId: string, payload: unknown): void;
669
1024
  }
670
1025
 
671
- /** Claim-checked filesystem operations. Paths are root-relative; canonical host paths never cross the seam. */
1026
+ /**
1027
+ * Claim-checked filesystem operations. Paths are root-relative; canonical host paths never cross
1028
+ * the seam. Writes are atomic: readers see the old file or the new one, never a partial write. A
1029
+ * replace keeps the file's mode but makes a new file, so owner and extended attributes are not
1030
+ * carried over. On a volume without hard links an exclusive create can briefly show an empty file.
1031
+ */
672
1032
  declare interface KayAgentFilesystem {
673
1033
  readText(location: KayAgentFilesystemLocation): Promise<string>;
674
- writeText(location: KayAgentFilesystemLocation, content: string): Promise<void>;
1034
+ writeText(location: KayAgentFilesystemLocation, content: string, options?: KayAgentFilesystemWriteOptions): Promise<void>;
1035
+ /** The whole file, or the requested window of it. */
1036
+ readBytes(location: KayAgentFilesystemLocation, range?: KayAgentFilesystemByteRange): Promise<Uint8Array>;
1037
+ writeBytes(location: KayAgentFilesystemLocation, bytes: Uint8Array, options?: KayAgentFilesystemWriteOptions): Promise<void>;
675
1038
  mkdir(location: KayAgentFilesystemLocation): Promise<void>;
676
1039
  readDir(location: KayAgentFilesystemLocation): Promise<readonly KayAgentFilesystemEntry[]>;
677
1040
  stat(location: KayAgentFilesystemLocation): Promise<KayAgentFilesystemStat>;
@@ -680,6 +1043,12 @@ declare interface KayAgentFilesystem {
680
1043
  realpath(location: KayAgentFilesystemLocation): Promise<string>;
681
1044
  }
682
1045
 
1046
+ /** A byte window of a file; `maxBytes` is capped by the host. */
1047
+ declare interface KayAgentFilesystemByteRange {
1048
+ readonly offset: number;
1049
+ readonly maxBytes: number;
1050
+ }
1051
+
683
1052
  declare interface KayAgentFilesystemEntry {
684
1053
  readonly name: string;
685
1054
  readonly kind: "file" | "directory" | "symlink" | "other";
@@ -697,6 +1066,27 @@ declare interface KayAgentFilesystemStat {
697
1066
  readonly modifiedAtMs: number;
698
1067
  }
699
1068
 
1069
+ declare interface KayAgentFilesystemWriteOptions {
1070
+ /** Fail with `already_exists` instead of replacing a file already at the location. */
1071
+ readonly exclusive?: boolean;
1072
+ }
1073
+
1074
+ declare interface KayAgentInteractionSurfaces {
1075
+ /**
1076
+ * Kay's built-in browser pane: the pages in the user's Bonsai tabs. The page-side credential
1077
+ * guard runs on every act, so a driver over this surface may declare
1078
+ * {@link INTERACTION_CREDENTIAL_GUARD}.
1079
+ */
1080
+ readonly browserPane: InteractionSurface;
1081
+ /**
1082
+ * The user's desktop through the platform's accessibility APIs: native apps as targets
1083
+ * (`app://<bundle id>`), their windows' controls as elements. On macOS Kay drives it with its
1084
+ * bundled accessibility worker under the app's Accessibility permission; on a platform with no
1085
+ * backend it reports itself unavailable.
1086
+ */
1087
+ readonly desktop: InteractionSurface;
1088
+ }
1089
+
700
1090
  /** One knowledge-index hit — a reference and within-list rank score, never a snippet. */
701
1091
  declare interface KayAgentKnowledgeHit {
702
1092
  readonly uri: string;
@@ -849,9 +1239,17 @@ declare interface KayAgentPluginApi {
849
1239
  * command it is about to run, and against the host's executable binding.
850
1240
  */
851
1241
  readonly process: KayAgentProcess;
1242
+ /**
1243
+ * Always present; the interaction surfaces Kay ships (its browser pane, and the desktop through
1244
+ * the platform accessibility APIs), each call checked against `interaction.surface {surface}`
1245
+ * and the session's permissions (see {@link InteractionSurface}).
1246
+ * Wrap one in an {@link InteractionDriver} and register it with
1247
+ * {@link KayAgentPluginApi.registerInteractionDriver} to make it drivable by the agent.
1248
+ */
1249
+ readonly interaction: KayAgentInteractionSurfaces;
852
1250
  /**
853
1251
  * Jev typed-decision inference, as an OPTIONAL accelerator with a REQUIRED fallback. Each call
854
- * is checked against `jev.use` — but a refused grant or a missing saved key both
1252
+ * is checked against `jev.use` — but a refused grant or no usable Jev provider both
855
1253
  * produce the same outcome: your `fallback` runs.
856
1254
  *
857
1255
  * Always present, on every host. That is the point: with no absent state to feature-detect,
@@ -859,6 +1257,14 @@ declare interface KayAgentPluginApi {
859
1257
  * guard it with `if (api.withJev !== undefined)`.
860
1258
  */
861
1259
  readonly withJev: WithJev;
1260
+ /**
1261
+ * The daemon's clock and the time zone the user is in, read together on each `snapshot()` call.
1262
+ * Always present and ungated: it confers no authority, since a plugin runs in the daemon's
1263
+ * process and can read the clock itself. Read the user's zone here, never from `Intl` or
1264
+ * `process.env.TZ`, and render user-facing times in it rather than an event's own zone. This is
1265
+ * the same zone-of-record port the session stamp uses; see {@link KayAppTime.snapshot}.
1266
+ */
1267
+ readonly time: KayAppTime;
862
1268
  /**
863
1269
  * Publish the plugin's declared events, for example so a default notification rule can tell the
864
1270
  * user what the agent just did. Always present, like {@link withJev}: there is nothing to
@@ -880,6 +1286,14 @@ declare interface KayAgentPluginApi {
880
1286
  * from `ctx.auth`. A provider without a binding reuses inference credentials or needs none.
881
1287
  */
882
1288
  registerSearchProvider(provider: SearchProvider): void;
1289
+ /**
1290
+ * Register an Interaction driver (a backend behind the `interaction_*` tools: something the agent
1291
+ * can read, click, type into, and open). The engine owns the tools, element ids, run loop,
1292
+ * permission checks, and screenshots; the driver owns only its I/O. Declare the driver's `id`
1293
+ * under `contributes.interactionDrivers` in kay-plugin.json first. A `browser` driver must set
1294
+ * `credentialGuard` (see {@link INTERACTION_CREDENTIAL_GUARD}).
1295
+ */
1296
+ registerInteractionDriver(driver: InteractionDriver): void;
883
1297
  /**
884
1298
  * Register a content converter (turns raw bytes of a mime type into agent-facing content behind the
885
1299
  * read tool's finalize stage). Parallel to the kernel's `registerConverter`. Converters are
@@ -1080,9 +1494,84 @@ declare interface KayAgentWhiteboardUpdateInput {
1080
1494
  readonly title?: string;
1081
1495
  }
1082
1496
 
1497
+ /**
1498
+ * `app.time` / `api.time`: host facts about time. Added to `kay.app` in SDK 0.12.1 and to
1499
+ * `kay.agent` with the calendar zone-of-record work: a plugin that calls it declares
1500
+ * `compatibility.sdk` `^0.12.1`, so an older Bonsai refuses a directory or repository plugin at
1501
+ * load instead of the call failing.
1502
+ */
1503
+ declare interface KayAppTime {
1504
+ /**
1505
+ * Reads the daemon's clock and the user's time zone now.
1506
+ *
1507
+ * Every call asks the host again and returns a new object; nothing is cached. Call it where you
1508
+ * need the value (inside a tool, a handler or a workflow step), not inside `activate()`: a value
1509
+ * read there is as old as the activation, and the host can run a bundled plugin's `activate()`
1510
+ * against a recording api that has no `time`, so a read there throws. The exception is a
1511
+ * directory plugin that declares a workflow with a cron `timezone`: `workflows.define` is legal
1512
+ * only during `activate()`, so it reads the zone there, and the schedule then keeps that zone.
1513
+ * Keep the result for as long as you use it, and not across a long wait (a sleep, a timer, a
1514
+ * queued run): the zone can change while you wait. On a daemon that runs on the user's machine,
1515
+ * a change of the process's `TZ` shows in the next call. A `TZ` that names a zone wins over the
1516
+ * operating system's zone, so a change of the operating-system zone does not show while the
1517
+ * process has one. When `TZ` is unset or does not name a zone, a change of the operating-system
1518
+ * zone is meant to show, but none has been seen on a real machine yet.
1519
+ *
1520
+ * A zone the host cannot determine is an answer (`zoneSource: "utc-fallback"`), not an error.
1521
+ *
1522
+ * Use this to find the zone, not `Intl.DateTimeFormat().resolvedOptions().timeZone`, which can
1523
+ * keep a zone the runtime read earlier, or `process.env.TZ`, which is often unset or not a zone.
1524
+ * Neither says that the zone is unknown.
1525
+ *
1526
+ * It does not use `this`, so it is safe to destructure.
1527
+ */
1528
+ readonly snapshot: () => KayAppTimeSnapshot;
1529
+ }
1530
+
1531
+ /**
1532
+ * The plugin-facing time contract: the daemon's clock and the time zone the user is in,
1533
+ * read together in one call.
1534
+ *
1535
+ * Ungated and always present, like `app.paths`. It confers no authority: a plugin runs in the
1536
+ * daemon's process and can already read the clock. What the door adds is the daemon's answer
1537
+ * for the USER's zone, and a label for the case where that answer is unknown.
1538
+ *
1539
+ * This is a leaf module. It lives beside both target SDKs (rather than inside `kay-app`) so the
1540
+ * `kay.agent` SDK can expose the same door without importing the `kay.app` surface, which pulls
1541
+ * that surface into every agent-facing bundle.
1542
+ */
1543
+ /** One reading of the daemon's clock and the user's time zone. Each call returns a new object. */
1544
+ declare interface KayAppTimeSnapshot {
1545
+ /** The daemon's clock when the snapshot was read: milliseconds since the Unix epoch. */
1546
+ readonly nowMs: number;
1547
+ /**
1548
+ * The time zone the user is in, as an IANA id in the spelling ICU resolves (`America/New_York`).
1549
+ * One zone can have two ids, and ICU picks one: `Asia/Kolkata` comes back as `Asia/Calcutta`. So
1550
+ * never compare zone ids with `===`. A first-party plugin compares with `sameZone` from
1551
+ * `@kay/time`. A plugin that imports only this SDK and node builtins resolves each id with
1552
+ * `new Intl.DateTimeFormat("en-US", { timeZone: id }).resolvedOptions().timeZone` and compares the
1553
+ * results. Pass `zone` as the `timeZone` of `Intl.DateTimeFormat` or `toLocaleString` to format a
1554
+ * time for the user.
1555
+ *
1556
+ * When `zoneSource` is `utc-fallback` the zone is unknown, and this value is `UTC` only because a
1557
+ * value is required.
1558
+ */
1559
+ readonly zone: string;
1560
+ /**
1561
+ * How the daemon found `zone`.
1562
+ * - `host`: the daemon runs on the user's machine, and `zone` is the zone the daemon process
1563
+ * runs in, read for this call: the process's `TZ` when that names a zone, otherwise the
1564
+ * operating system's zone.
1565
+ * - `utc-fallback`: the daemon does not know the user's zone. It runs on another machine (a remote
1566
+ * daemon), or the machine's zone could not be read or is not an IANA id. `zone` is `UTC`, which
1567
+ * here is a placeholder and not a fact about the user: do not show it to the user as their zone.
1568
+ */
1569
+ readonly zoneSource: "host" | "utc-fallback";
1570
+ }
1571
+
1083
1572
  declare type KnownApi = "openai-completions" | "mistral-conversations" | "openai-responses" | "azure-openai-responses" | "openai-codex-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-vertex" | "pi-messages";
1084
1573
 
1085
- declare type KnownProvider = "amazon-bedrock" | "ant-ling" | "anthropic" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "radius" | "nvidia" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "zai-coding-cn" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "together" | "baseten" | "opencode" | "opencode-go" | "kimi-coding" | "meta" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "qwen-token-plan" | "qwen-token-plan-cn" | "qwen-token-plan-individual" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
1574
+ declare type KnownProvider = "amazon-bedrock" | "ant-ling" | "anthropic" | "google" | "google-vertex" | "openai" | "azure-openai-responses" | "openai-codex" | "radius" | "typesafe" | "nvidia" | "deepseek" | "github-copilot" | "xai" | "groq" | "cerebras" | "openrouter" | "vercel-ai-gateway" | "zai" | "zai-coding-cn" | "mistral" | "minimax" | "minimax-cn" | "moonshotai" | "moonshotai-cn" | "huggingface" | "fireworks" | "together" | "baseten" | "opencode" | "opencode-go" | "kimi-coding" | "meta" | "cloudflare-workers-ai" | "cloudflare-ai-gateway" | "qwen-token-plan" | "qwen-token-plan-cn" | "qwen-token-plan-individual" | "xiaomi" | "xiaomi-token-plan-cn" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-sgp";
1086
1575
 
1087
1576
  declare interface LineRange {
1088
1577
  readonly start: number;
@@ -1133,29 +1622,26 @@ declare interface MistralConversationsCompat {
1133
1622
  supportsMidConvoSystemMessages?: boolean;
1134
1623
  }
1135
1624
 
1136
- declare interface Model<TApi extends Api> {
1137
- id: string;
1138
- name: string;
1139
- api: TApi;
1140
- provider: ProviderId;
1141
- baseUrl: string;
1625
+ /** Chat model: usable with `stream()` and friends. */
1626
+ declare interface Model<TApi extends Api> extends BaseModel<TApi> {
1627
+ /**
1628
+ * Optional: chat is the default model type, so models without `type` are chat
1629
+ * models. Narrow mixed model lists with `isModelType()` instead of comparing
1630
+ * `type` directly.
1631
+ */
1632
+ type?: "chat";
1142
1633
  reasoning: boolean;
1143
1634
  /**
1144
1635
  * Maps pi thinking levels to provider/model-specific values.
1145
1636
  * Missing keys use provider defaults. null marks a level as unsupported.
1146
1637
  */
1147
1638
  thinkingLevelMap?: ThinkingLevelMap;
1148
- input: ("text" | "image")[];
1149
- /** Provider input limits and cache-safe preprocessing metadata. */
1150
- inputLimits?: ModelInputLimits;
1151
- cost: ModelCost;
1152
1639
  /** Prompt cache lifetimes per retention tier. Unset when the provider's cache behavior is unknown. */
1153
1640
  promptCache?: ModelPromptCache;
1154
1641
  contextWindow: number;
1155
1642
  maxTokens: number;
1156
1643
  /** Default sampling parameters for this model. See {@link StreamOptions.samplingParams}; per-request keys override these. */
1157
1644
  samplingParams?: Record<string, unknown>;
1158
- headers?: Record<string, string>;
1159
1645
  /** Compatibility overrides for OpenAI-compatible APIs. If not set, auto-detected from baseUrl. */
1160
1646
  compat?: TApi extends "openai-completions" ? OpenAICompletionsCompat : TApi extends "openai-responses" | "azure-openai-responses" | "openai-codex-responses" ? OpenAIResponsesCompat : TApi extends "anthropic-messages" ? AnthropicMessagesCompat : TApi extends "bedrock-converse-stream" ? BedrockCompat : TApi extends "mistral-conversations" ? MistralConversationsCompat : never;
1161
1647
  }
@@ -1501,8 +1987,15 @@ declare interface PluginDelegatedScheme {
1501
1987
  /** Run the connect flow and mint the material to store. Throw to fail the connect. */
1502
1988
  readonly connect: (ctx: PluginDelegatedConnectContext) => Promise<PluginDelegatedConnectResult>;
1503
1989
  /**
1504
- * Mint fresh material from the current (expiring) material. Absent means non-refreshable:
1505
- * expired material resolves as-is (like an OAuth scheme without a refresh token).
1990
+ * Mint fresh material from the current (expiring) material, including the `data` the last
1991
+ * connect or refresh returned. Kay calls this only inside its credential authority, which holds
1992
+ * the refresh lease and saves the result; this hook performs the exchange and nothing else.
1993
+ * Returned `data` replaces the stored `data`; omitted `data` keeps it. Returned `expiresAt`
1994
+ * replaces the stored expiry; omitted `expiresAt` means the new material does not expire.
1995
+ * Throw {@link DelegatedRefreshRejectedError} when the provider refused the grant for good (the
1996
+ * user must reconnect); any other throw is retried with backoff and keeps the account connected.
1997
+ * Absent means non-refreshable: expired material resolves as-is (like an OAuth scheme without a
1998
+ * refresh token).
1506
1999
  */
1507
2000
  readonly refresh?: (material: PluginDelegatedSecretMaterial) => Promise<PluginDelegatedSecretMaterial>;
1508
2001
  /**
@@ -2081,6 +2574,19 @@ declare interface ToolCallModel {
2081
2574
  */
2082
2575
  declare type ToolCallOrigin = "model" | "host";
2083
2576
 
2577
+ /** A call to judge with {@link ToolContext.checkToolCallPermission}. */
2578
+ declare interface ToolCallPermissionCheck {
2579
+ readonly toolName: string;
2580
+ readonly input: Record<string, unknown>;
2581
+ }
2582
+
2583
+ declare type ToolCallPermissionVerdict = {
2584
+ readonly allowed: true;
2585
+ } | {
2586
+ readonly allowed: false;
2587
+ readonly reason: string;
2588
+ };
2589
+
2084
2590
  /**
2085
2591
  * What a tool call ADDRESSES, resolved by the session before execution from facts it already
2086
2592
  * holds for auth scoping: a URI verb's matched module (and the extension that registered it), or
@@ -2123,6 +2629,13 @@ declare interface ToolContext {
2123
2629
  * behind itself. Present only with a live session. One request may be pending at a time.
2124
2630
  */
2125
2631
  readonly requestMidTurnCompaction?: (customInstructions?: string, trigger?: "command" | "suggestion") => MidTurnCompactionRequest;
2632
+ /**
2633
+ * Put a call this tool is about to cause through the session's permission gate, as if the agent
2634
+ * had made it directly: classification, rules, mode, the auto approver, and the approval card.
2635
+ * The call is only judged, never executed. For a tool whose effect is only knowable at run time
2636
+ * (a click in a web page that turns out to send an email). Present only with a live session.
2637
+ */
2638
+ readonly checkToolCallPermission?: (call: ToolCallPermissionCheck) => Promise<ToolCallPermissionVerdict>;
2126
2639
  /**
2127
2640
  * Re-root the session into `newCwd` mid-run, recomposing the cwd-scoped substrate (builtin tools,
2128
2641
  * the capability registry, the system-prompt project line) in place so subsequent tool calls operate
@@ -2692,6 +3205,21 @@ declare interface UriModule {
2692
3205
  */
2693
3206
  readonly title?: string;
2694
3207
  readonly fallback?: boolean;
3208
+ /**
3209
+ * Whether this module parses the read tool's trailing line selector out of its own addresses.
3210
+ * Absent or false: the host owns the explicit forms (`:50-200`, `:50-`, `:50+10`, `:raw`). The
3211
+ * read tool strips one before dispatch, hands `read` the bare address, and applies the window
3212
+ * to what comes back, so a module never has to know the grammar for the read tool's own
3213
+ * `Continue with '<address>:N-M'` banner to work on its addresses (#10754). A bare trailing
3214
+ * `:<digits>` is never taken from such a module: it is too common inside real ids (a Figma
3215
+ * node `1:2`), so it reaches the module as written.
3216
+ *
3217
+ * Declare `true` only when the module must see the raw address: its grammar can end in an
3218
+ * explicit-range shape, it wants the bare `:50` form too, or it windows the content itself
3219
+ * (streaming a large file, skipping a cap it applies to an unwindowed read). Such a module
3220
+ * calls `parseLineSelector` itself and reports what it did through `RawResource.selector`.
3221
+ */
3222
+ readonly ownsLineSelectors?: boolean;
2695
3223
  readonly claims?: readonly UriClaim[];
2696
3224
  /** Migration-only prompt summary until structured help/catalog coverage replaces prose. */
2697
3225
  readonly description?: string;
@@ -2701,14 +3229,17 @@ declare interface UriModule {
2701
3229
  /** Pure route/payload validation applied before a consequential permission ask. */
2702
3230
  readonly preflight?: UriOperationPreflightPolicy;
2703
3231
  /**
2704
- * Structured facts naming the WHOLE consequence of this call, or `undefined` when
2705
- * the route cannot honestly be described on a one-line approval surface. The module
3232
+ * Structured facts for approval surfaces, or `undefined` when unavailable.
3233
+ * A one-line description must name the whole consequence; `preview` instead
3234
+ * supplies expanded, literal-text detail and suppresses one-line approval. The module
2706
3235
  * owns the static action phrase, target resolution, payload decoding and declaration
2707
3236
  * of covered payload fields and query parameters. Unknown fields cost the summary.
2708
3237
  * The host owns framing, the whole-quote fit (no elision) and display-safety admission.
2709
3238
  *
2710
3239
  * Unlike preflight, this hook is async and may do I/O. Resolve names through `ctx`,
2711
- * the same dispatch context used by execution, never an ambient or cached account.
3240
+ * the same dispatch context used by execution, never an ambient or cached account. A name
3241
+ * the module resolved under that account may be cached for a bounded time, keyed by that
3242
+ * account's identity and never answered to another account or after a failed lookup.
2712
3243
  * The host calls it once before raising the ask, enforces the short `ctx.signal`
2713
3244
  * deadline and discards late answers. A throw yields no summary and a module-attributed
2714
3245
  * diagnostic; declining costs a click into the app, never an under-described approval.
@@ -2718,6 +3249,26 @@ declare interface UriModule {
2718
3249
  readonly uri: string;
2719
3250
  readonly payload: unknown;
2720
3251
  }, ctx: UriResolveContext): Promise<UriOperationDescription | undefined>;
3252
+ /**
3253
+ * A short, render-only name for the resource a settled call on `request.uri` touched, which the
3254
+ * host stamps on the tool result's `details` so a transcript row can name it where it would
3255
+ * otherwise print the address (`#lane-analytics` and `thread`, not a channel id). Answer
3256
+ * `undefined` for any address you cannot honestly name.
3257
+ *
3258
+ * Called once per successful URI verb call, AFTER the verb handler has returned, with the same
3259
+ * dispatch context. Resolve names through `ctx` and nothing else, and prefer a cache your own
3260
+ * verb handlers warm, keyed by the executing account: a name one account saw is never answered
3261
+ * to another, and a failed, blank or aborted lookup is never cached. The host bounds the wait
3262
+ * (a finished result is held at most a quarter of a second), gives `ctx.signal` a hard deadline,
3263
+ * and drops a late, thrown or refused answer without failing the call; a throw is reported as a
3264
+ * module-attributed diagnostic. The label is a label, never an identity: the address remains the
3265
+ * click target, hover text and accessible name. It is UI-only, so it never enters the tool
3266
+ * result's content and no telemetry reads it.
3267
+ *
3268
+ * Unlike `describeOperation` this is not an approval surface: it carries no consequence, no
3269
+ * quote and no coverage claim, and nothing may gate a permission on it.
3270
+ */
3271
+ labelResource?(request: UriResourceLabelRequest, ctx: UriResolveContext): Promise<UriResourceLabel | undefined>;
2721
3272
  /**
2722
3273
  * Per-resource observability: does a `format: "observation"` read of THIS
2723
3274
  * uri render source-native material ({@link UriResolveContext.format})?
@@ -2838,6 +3389,18 @@ declare interface UriOperationDescription {
2838
3389
  readonly target: string;
2839
3390
  /** Model-authored content; the host flattens, budgets, elides and bounds it. */
2840
3391
  readonly quote?: string;
3392
+ /** Full, render-only detail. Hosts must not turn a rich preview into one-line approval. */
3393
+ readonly preview?: {
3394
+ readonly title: string;
3395
+ readonly description?: string;
3396
+ readonly fields?: readonly {
3397
+ readonly label: string;
3398
+ readonly value: string;
3399
+ readonly oldValue?: string;
3400
+ }[];
3401
+ };
3402
+ /** True only when every displayed fact comes from the input, independent of the executing account. */
3403
+ readonly accountIndependent?: true;
2841
3404
  /** What this description accounts for; the host refuses undeclared input. */
2842
3405
  readonly accounts: {
2843
3406
  /** The payload actually read by the module, after its own decoding. */
@@ -3101,6 +3664,24 @@ declare interface UriResourceDescription {
3101
3664
  readonly sourceUrl?: string;
3102
3665
  }
3103
3666
 
3667
+ /**
3668
+ * What a settled call touched, as a person would name it.
3669
+ *
3670
+ * `name` is the resource itself (`#lane-analytics`). `section` is the part of it the address
3671
+ * reaches into (`thread`), and is absent when the address is the resource.
3672
+ */
3673
+ declare interface UriResourceLabel {
3674
+ readonly name: string;
3675
+ readonly section?: string;
3676
+ }
3677
+
3678
+ /** The call a module is asked to label. */
3679
+ declare interface UriResourceLabelRequest {
3680
+ readonly operation: UriOperation;
3681
+ /** The dispatch address: selector-stripped, the exact form the module's verb handler received. */
3682
+ readonly uri: string;
3683
+ }
3684
+
3104
3685
  declare interface UriSchemeClaim {
3105
3686
  readonly kind: "scheme";
3106
3687
  readonly scheme: string;
@@ -3417,8 +3998,8 @@ declare type WhiteboardValue = z.infer<typeof WhiteboardValue>;
3417
3998
  /**
3418
3999
  * Run `use` against a live Jev client, falling back to `fallback` whenever Jev cannot serve the
3419
4000
  * call. The wrapper exists so a feature can ASK for a typed decision without ever depending on
3420
- * one: `fallback` is required, and it runs whenever no host-managed key is saved,
3421
- * the user has not granted `jev.use`, the branch exceeds its budget, `use` throws, or
4001
+ * one: `fallback` is required, and it runs whenever no provider in the user's Jev provider
4002
+ * order holds a usable credential, the user has not granted `jev.use`, the branch exceeds its budget, `use` throws, or
3422
4003
  * `use` declines by returning `undefined`. With valid options, this rejects on an explicit
3423
4004
  * `signal` abort, reaching `deadlineAt`, or `fallback` itself throwing.
3424
4005
  *
@@ -3426,10 +4007,9 @@ declare type WhiteboardValue = z.infer<typeof WhiteboardValue>;
3426
4007
  * {@link createFallbackOnlyWithJev}, so a caller never branches on availability — branching is
3427
4008
  * exactly how a fallback gets skipped.
3428
4009
  *
3429
- * `jev` is the vendor's own `TypeSafeClient`, configured with the host's default model and base
3430
- * URL. Native `request.model` overrides remain supported. It
3431
- * is valid only for the duration of the callback: a reference retained past `use`'s scope is
3432
- * refused at the transport, and an in-flight request is aborted when the call stops waiting.
4010
+ * `jev` is a {@link JevClient} on the host's Jev model. It is valid only for the duration of
4011
+ * the callback: a reference retained past `use`'s scope is refused at the transport, and an
4012
+ * in-flight request is aborted when the call stops waiting.
3433
4013
  * The callback itself is ABANDONED, not interrupted — a host cannot stop arbitrary JavaScript
3434
4014
  * that is already running, so `use` must not perform side effects it would mind completing
3435
4015
  * unobserved. The same applies to a running fallback: abort/deadline stops waiting for it but
@@ -3438,7 +4018,7 @@ declare type WhiteboardValue = z.infer<typeof WhiteboardValue>;
3438
4018
  declare type WithJev = <T>(
3439
4019
  /** Return `undefined` to decline (the fallback runs). A feature whose real result can itself be
3440
4020
  * `undefined` should return `null` or its own wrapper type instead. */
3441
- use: (jev: TypeSafeClient) => T | undefined | PromiseLike<T | undefined>,
4021
+ use: (jev: JevClient) => T | undefined | PromiseLike<T | undefined>,
3442
4022
  /** Required. The behaviour the feature has without Jev. */
3443
4023
  fallback: () => T | PromiseLike<T>, options?: WithJevOptions) => Promise<T>;
3444
4024