@oberik/sdk 0.1.0 → 0.3.0

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.
@@ -82,6 +82,8 @@ export interface ClientOptions {
82
82
  headers?: Record<string, string>;
83
83
  /** Client-side tools registered up-front; auto-dispatched by chat.run/stream. */
84
84
  tools?: ClientToolDef[];
85
+ /** UI components the agent can draw into your app. Needs the `ui_tools` capability. */
86
+ ui?: UiComponent<any>[];
85
87
  }
86
88
  /** Public tenant view — never includes the API key. */
87
89
  export interface TenantOut {
@@ -540,10 +542,86 @@ export interface QuestionAnswer {
540
542
  }
541
543
  /** Answer a batch of questions. Return one entry per question (or a single
542
544
  * `{ chat_instead: true }` to decline the whole batch and keep chatting). */
545
+ /** An Agent Plugin visible to this token: what the project published, plus anything
546
+ * this end-user uploaded. */
547
+ export interface PublishedPlugin {
548
+ id: string;
549
+ name: string;
550
+ version?: string | null;
551
+ description: string;
552
+ /** `tenant` = published by the project to everyone; `private` = this user's own. */
553
+ visibility: string;
554
+ owner_subject?: string | null;
555
+ enabled: boolean;
556
+ size_bytes: number;
557
+ /** Each skill's name and description are all the agent sees until it loads one, so
558
+ * the description is the line that decides whether a procedure ever gets used. */
559
+ skills: {
560
+ name: string;
561
+ description: string;
562
+ files: string[];
563
+ }[];
564
+ mcp_servers: string[];
565
+ /** Wrong but not fatal — a skill that would not parse, a server that was refused.
566
+ * Worth surfacing: a plugin whose skill silently did not load is the failure people
567
+ * spend an afternoon on. */
568
+ warnings: string[];
569
+ }
570
+ /** A component your app can render, declared for the agent to call.
571
+ *
572
+ * The difference from a client tool is that there is no result. A client tool is work
573
+ * handed back — the turn stops, you run it, the answer returns. This is a one-way
574
+ * instruction: the agent calls it, your `render` runs, and the turn carries straight
575
+ * on. Use it wherever showing beats describing. */
576
+ export interface UiComponent<A = Record<string, unknown>> {
577
+ name: string;
578
+ /** What it shows, and when the agent should reach for it. The agent picks from this,
579
+ * so "Draw a line chart of a numeric series over time" beats "chart". */
580
+ description: string;
581
+ /** JSON Schema for the arguments, exactly as a client tool declares them. */
582
+ parameters?: Record<string, unknown>;
583
+ /** Called when the agent draws it. Return nothing — anything you return is dropped,
584
+ * because the agent is not waiting. */
585
+ render: (args: A) => void;
586
+ }
587
+ /** One thing the agent drew, as it comes back on the turn. */
588
+ export interface UiRender {
589
+ name: string;
590
+ args: Record<string, unknown>;
591
+ }
592
+ /** Something the agent stopped to ask permission for.
593
+ *
594
+ * Arrives with `requires_action` and an EMPTY `tool_calls`: an approval is not the
595
+ * client's work to execute, so tool auto-dispatch can never grant one on the user's
596
+ * behalf. It has to reach a person. */
597
+ export interface PendingApproval {
598
+ tool_call_id: string;
599
+ /** One line saying exactly what the agent will do, with the specifics in it. */
600
+ action: string;
601
+ /** What the person needs in order to decide — the message, the command, the rows. */
602
+ detail?: string | null;
603
+ /** What cannot easily be undone. Usually the only part that decides the answer. */
604
+ consequence?: string | null;
605
+ }
606
+ /** A person's answer to one approval pause. */
607
+ export interface ApprovalDecision {
608
+ tool_call_id?: string;
609
+ approved: boolean;
610
+ /** The difference between "no" and "no, use the other address". */
611
+ note?: string | null;
612
+ }
613
+ /** Put the request to a person and return their decision.
614
+ *
615
+ * Throwing, or returning `false`, is a refusal — and a refusal stops the work: the
616
+ * agent is told not to try another way and to ask what you want instead. */
617
+ export type ApprovalHandler = (request: PendingApproval) => ApprovalDecision | boolean | Promise<ApprovalDecision | boolean>;
543
618
  export type QuestionHandler = (pending: PendingQuestions) => QuestionAnswer | QuestionAnswerItem[] | Promise<QuestionAnswer | QuestionAnswerItem[]>;
544
619
  export interface RunOptions extends Omit<ChatRequest, "tool_results" | "client_tools"> {
545
620
  /** Extra client tools for this call (merged over any registered on the client). */
546
621
  tools?: ClientToolDef[];
622
+ /** UI components for this call (merged over any registered on the client). Needs the
623
+ * `ui_tools` capability. */
624
+ ui?: UiComponent<any>[];
547
625
  /** Max auto tool-dispatch rounds before giving up (default 10). */
548
626
  maxToolRounds?: number;
549
627
  /** Fires before each batch of client tools is executed. */
@@ -551,10 +629,24 @@ export interface RunOptions extends Omit<ChatRequest, "tool_results" | "client_t
551
629
  /** Answer the agent's questions and continue the turn automatically. Without it,
552
630
  * `run` returns as soon as the agent asks, with `questions` set. */
553
631
  onQuestion?: QuestionHandler;
632
+ /** Decide the agent's approval requests and continue the turn. Without it, `run`
633
+ * returns as soon as the agent asks, with `approvals` set — which is the right
634
+ * default: nothing should be able to approve an irreversible action by accident. */
635
+ onApproval?: ApprovalHandler;
554
636
  signal?: AbortSignal;
555
637
  }
556
- /** A non-text input part (any modality the model supports). `url` is a data: URI or
557
- * an https URL. Gated by the token's `input:<kind>` capability. */
638
+ /** A non-text input part. `url` is a data: URI or an https URL.
639
+ *
640
+ * Two capabilities decide what happens to it, and they are separate on purpose:
641
+ *
642
+ * - `input:file` accepts ANY attachment — a PNG and a WAV included. What the model
643
+ * cannot perceive is read as text instead (OCR for a scan, a transcript, an
644
+ * extraction), so granting it costs fidelity, never access.
645
+ * - `input:image` / `input:audio` / `input:video` additionally let the model look at
646
+ * that kind with its own senses, raw.
647
+ *
648
+ * So a project that just wants users to attach whatever they have grants `input:file`;
649
+ * one that wants the model to actually SEE the picture grants `input:image` too. */
558
650
  export interface Attachment {
559
651
  /** Stable identity, set on anything the agent produced. Match on this, not on `url`:
560
652
  * a URL is signed per response, so the same file arrives with a different one from
@@ -623,14 +715,39 @@ export interface ChatRequest {
623
715
  messages?: string[] | null;
624
716
  /** Multimodal inputs (images/audio/video/files) for this turn. */
625
717
  attachments?: Attachment[] | null;
626
- /** Modalities the model may return, e.g. ["text","image"]. Bounded by the token's
627
- * output:<modality> capabilities; ignored by text-only models. */
718
+ /** Modalities the MODEL may generate this turn, e.g. ["text","image"]. Bounded by the
719
+ * token's `output:<modality>` capabilities; ignored by text-only models.
720
+ *
721
+ * Nothing to do with what the agent may hand over as a file: a screenshot it took or
722
+ * a chart it rendered in a sandbox is a file it produced, not a modality it emitted,
723
+ * and `output:file` is what permits sending those — any of them, pictures included. */
628
724
  output_modalities?: string[] | null;
629
725
  tool_results?: ClientToolResult[] | null;
630
726
  /** Resume a turn paused on `ask_user`. The wording the agent reads is composed
631
727
  * server-side from the question it actually asked, so the transcript can't drift
632
728
  * from what was on screen. */
633
729
  question_answers?: QuestionAnswer[] | null;
730
+ /** UI components the agent may draw this turn. `chat.run`/`chat.stream` fill this in
731
+ * from the components you registered — set it directly only on a raw `/chat` call. */
732
+ ui_tools?: Record<string, unknown>[] | null;
733
+ /** Answers to approval pauses, resuming the turn. */
734
+ approval_decisions?: ApprovalDecision[] | null;
735
+ /** Let the agent stop and ask permission before an action with consequences. Needs
736
+ * the `approvals` capability. Set false for any caller that cannot answer — a batch
737
+ * job, a webhook — so the agent never stalls waiting on a decision. */
738
+ enable_approvals?: boolean;
739
+ /** Let the agent reach for the skills published to this project. Needs the `plugins`
740
+ * capability. */
741
+ /** Let the agent call the tools this project publishes as URLs — Oberik POSTs to
742
+ * the customer's endpoint with the end-user's identity signed into the body. Needs
743
+ * the `webhook_tools` capability. Turn it off for a turn that should not touch your
744
+ * systems. */
745
+ enable_webhook_tools?: boolean;
746
+ enable_plugins?: boolean;
747
+ /** Which plugins this turn may use, by name. Omit for everything the token can see.
748
+ * Narrowing only — a name that is not visible is absent rather than an error, so a
749
+ * pinned list does not start failing turns the day someone deletes one. */
750
+ plugins?: string[] | null;
634
751
  /** The user pressed Done on a page the agent handed them. Resumes a turn that
635
752
  * stopped on a blocking hand-off — the button IS the answer, so nothing else is
636
753
  * needed with it. */
@@ -661,8 +778,6 @@ export interface ChatRequest {
661
778
  /** Attach a specific existing sandbox to this turn (reconnect to a prior session).
662
779
  * Omitted = the sandbox bound to this chat session, else a fresh one. */
663
780
  computer_session_id?: string | null;
664
- /** Let the agent run read-only SQL against connected query-mode data sources. */
665
- enable_data_query?: boolean;
666
781
  /** Let the agent drive a real browser — click, type, scroll, wait, capture — rather
667
782
  * than fetching one page at a time. Needs the `browser` capability and the browser
668
783
  * service; `web_search`/`browse_url` work without it. */
@@ -692,7 +807,27 @@ export interface PendingToolCall {
692
807
  name: string;
693
808
  args: Record<string, unknown>;
694
809
  }
810
+ /** One sentence, and the passages it came from.
811
+ *
812
+ * `citations` is still the whole retrieval set — a client may want to show what was
813
+ * searched — but this is what answers "where did THAT come from". Before it existed the
814
+ * docs promised a citation "for every claim" and delivered the retrieval set in score
815
+ * order, so a question whose answer lived in one chunk came back with three "sources" and
816
+ * a UI built as instructed showed the reader citations that did not support the sentence. */
817
+ export interface Claim {
818
+ text: string;
819
+ /** Offsets into `content`. Null when the claim could not be located after markers were
820
+ * stripped — better absent than pointing at the wrong span. */
821
+ start: number | null;
822
+ end: number | null;
823
+ /** `Citation.marker` values, in the order the model wrote them. */
824
+ citations: number[];
825
+ }
695
826
  export interface Citation {
827
+ /** The number the model was shown for this passage, and what a `Claim` refers to. */
828
+ marker?: number | null;
829
+ /** Whether a sentence was actually attributed to it. */
830
+ used?: boolean;
696
831
  document_id: string;
697
832
  chunk_index: number;
698
833
  filename: string | null;
@@ -701,10 +836,9 @@ export interface Citation {
701
836
  quote: string | null;
702
837
  }
703
838
  /** A unified source for UI rendering (clickable pill) — a document citation, a
704
- * web result/page, or a database query. `type` = "document" | "web" | "query"
705
- * (query sources carry the executed SQL in `snippet` for verifiability). */
839
+ * web result/page. `type` = "document" | "web". */
706
840
  export interface Source {
707
- type: "document" | "web" | "query" | string;
841
+ type: "document" | "web" | string;
708
842
  title?: string | null;
709
843
  url?: string | null;
710
844
  snippet?: string | null;
@@ -723,6 +857,10 @@ export interface ChatResponse {
723
857
  * auto-dispatch can never answer it on the user's behalf. Resume by sending
724
858
  * `question_answers` (or let `chat.run`/`chat.stream` do it via `onQuestion`). */
725
859
  questions: PendingQuestions[];
860
+ /** The agent stopped before doing something and is waiting to be allowed. Same shape
861
+ * of pause as `questions`, and likewise never in `tool_calls`. Resume by sending
862
+ * `approval_decisions` (or let `chat.run`/`chat.stream` do it via `onApproval`). */
863
+ approvals: PendingApproval[];
726
864
  /** The agent's plan for this session, when the todo family ran this turn. */
727
865
  todos: TodoItem[];
728
866
  /** Every subagent of this conversation, including ones still working — a turn ending
@@ -731,17 +869,41 @@ export interface ChatResponse {
731
869
  /** Set only when history had to be trimmed or summarized to fit the window. */
732
870
  context?: ContextReport | null;
733
871
  citations: Citation[];
872
+ /** Sentences attributed to a passage, in order. Empty when the model emitted no markers,
873
+ * which `attribution` reports rather than hiding. */
874
+ claims: Claim[];
875
+ /** `per-claim` · `retrieval-only` · `none`. Draw footnotes from `claims` on the first;
876
+ * on the second you have the retrieval set and no attribution, which is a different
877
+ * thing to show. */
878
+ attribution: "per-claim" | "retrieval-only" | "none";
879
+ /** Set when the agent put a page in front of the user. Declared here as well as on
880
+ * `ChatDone`: a `chat.send()` caller could not learn a hand-off had happened at all,
881
+ * while the docs say the blocking path carries `handoff.blocking`. */
882
+ handoff?: Handoff | null;
734
883
  sources: Source[];
735
884
  /** Non-text outputs of the turn: generated media, and files the agent exported
736
885
  * from its sandbox. `url` is signed fresh per response — don't persist it. */
737
886
  attachments: Attachment[];
887
+ /** Components the agent drew this turn, in call order. `chat.run`/`chat.stream` have
888
+ * already called each component's `render` by the time you see this; it is here so a
889
+ * client that re-mounts can redraw from the list. */
890
+ ui: UiRender[];
738
891
  /** Guardrail actions taken this turn, e.g. "pii:EMAIL", "ungrounded", "injection". */
739
892
  guard_flags: string[];
740
893
  /** A reasoning model's thinking for this turn, else "". Live only — it is not stored
741
894
  * and is never sent back to the model, so it won't appear in session history. */
742
895
  reasoning?: string;
743
- /** Non-normal stop reason, else null. "max_tool_iterations" when a token's
744
- * tool-loop ceiling was hit. Language-neutral — localize it yourself. */
896
+ /**
897
+ * Non-normal stop reason, else null. Language-neutral — localize it yourself.
898
+ *
899
+ * `max_tool_iterations` a token's tool-loop ceiling was hit; `content` is the answer
900
+ * produced so far
901
+ * `guardrail` an input guardrail refused the message
902
+ * `no_model` the PROJECT has no model configured, so nothing could run.
903
+ * Not the end-user's problem and not something they can fix —
904
+ * route it to whoever set the project up. `GET /readiness` on
905
+ * the control plane lists what is missing.
906
+ */
745
907
  finish_reason?: string | null;
746
908
  }
747
909
  export interface SessionOut {
@@ -797,42 +959,139 @@ export interface TaskOut {
797
959
  last_error: string | null;
798
960
  created_at: string;
799
961
  }
800
- export type ConnectorType = "postgres" | "http" | string;
801
- export interface SyncConfig {
962
+ /** A project's guardrail policy, complete an untouched field reads as its default
963
+ * rather than as missing. */
964
+ export interface GuardrailPolicy {
965
+ enabled: boolean;
966
+ input: {
967
+ injection: boolean;
968
+ blockedTopics: string[];
969
+ pii: PiiMode;
970
+ };
971
+ output: {
972
+ groundedness: boolean;
973
+ moderation: boolean;
974
+ pii: PiiMode;
975
+ };
976
+ /** Which model judges. Null = the turn's own model. */
977
+ guardModel: string | null;
978
+ /** `block` refuses the turn; `flag` allows it and annotates `guard_flags`. */
979
+ onViolation: "block" | "flag";
980
+ }
981
+ /** Off is the default and deliberately so: on a product where an end-user shares their own
982
+ * information on purpose, the model has to see it. `detect` records that it was there;
983
+ * `redact` keeps it from the provider and costs the agent the ability to use it. */
984
+ export type PiiMode = "off" | "detect" | "redact";
985
+ /** Flat, because that is what a form sends. Anything omitted is left unchanged. */
986
+ export interface GuardrailUpdate {
987
+ enabled?: boolean;
988
+ injection?: boolean;
989
+ blockedTopics?: string[];
990
+ inputPii?: PiiMode;
991
+ groundedness?: boolean;
992
+ moderation?: boolean;
993
+ outputPii?: PiiMode;
994
+ guardModel?: string;
995
+ onViolation?: "block" | "flag";
996
+ }
997
+ /** What still has to happen before a project can answer a question. */
998
+ export interface ProjectReadiness {
999
+ ready: boolean;
1000
+ /** The first blocking step, so one line of UI has something to render. */
1001
+ next: ReadinessStep | null;
1002
+ steps: ReadinessStep[];
1003
+ }
1004
+ export interface ReadinessStep {
1005
+ id: string;
1006
+ done: boolean;
1007
+ /** What stops working while this is undone — "every request", "every document upload". */
1008
+ blocks: string;
1009
+ what: string;
1010
+ /** The one call, or the one screen, that fixes it. */
1011
+ how: string;
1012
+ }
1013
+ export interface ConnectInfo {
1014
+ projectId: string;
1015
+ tenantId: string | null;
1016
+ controlPlaneUrl: string;
1017
+ dataPlaneUrl: string;
1018
+ /** Copy-pasteable: the server half and the app half, using both clients correctly. */
1019
+ snippet: string;
1020
+ }
1021
+ /** What a project key may do. An admin key satisfies a mint requirement, never the
1022
+ * reverse. */
1023
+ export type ProjectKeyScope = "admin" | "mint";
1024
+ export interface ProjectKeyInfo {
1025
+ id: string;
802
1026
  name: string;
803
- connector_type: ConnectorType;
804
- /**
805
- * "sync" (default) copies records into the vector store (RAG over the data).
806
- * "query" is live query-in-place: the agent runs read-only SQL against the source
807
- * at question time — the right mode for structured/analytical data (postgres only).
808
- */
809
- mode?: "sync" | "query";
810
- /** Connector-specific config (dsn/query, token/repo, ...). Holds secrets. */
811
- config: Record<string, unknown>;
812
- /** Auto-refresh cadence in seconds; 0 = manual only. Default 3600. Ignored for query mode. */
813
- sync_interval_seconds?: number;
814
- tags?: string[];
815
- visibility?: "self" | "private" | "shared" | "groups" | "tenant";
816
- visibility_scope?: string | null;
817
- acl_roles?: string[];
818
- acl_groups?: string[];
1027
+ scope: ProjectKeyScope;
1028
+ /** The visible stub, e.g. "pk_ab12…". The key itself is shown once, at creation. */
1029
+ prefix: string;
1030
+ lastUsedAt: string | null;
1031
+ createdAt: string;
1032
+ }
1033
+ /** What a token holds, and which per-turn switches are worth setting on it. */
1034
+ export interface TokenCapabilities {
1035
+ subject: string;
1036
+ scope: string;
1037
+ /** `null` = unrestricted (a service key), which is NOT the same as an empty list. */
1038
+ capabilities: string[] | null;
1039
+ flags: {
1040
+ flag: string;
1041
+ /** Any one of these is enough to grant it. Empty = ungated. */
1042
+ capability: string[];
1043
+ granted: boolean;
1044
+ platform_enabled: boolean;
1045
+ /** Whether setting this flag on a request would change anything. */
1046
+ effective: boolean;
1047
+ label: string;
1048
+ description: string;
1049
+ group: string | null;
1050
+ }[];
1051
+ modalities: {
1052
+ /** What may be attached. */
1053
+ input: string[];
1054
+ /** What the model perceives directly; the rest arrives as extracted text. */
1055
+ native: string[];
1056
+ /** What the agent may hand back. */
1057
+ output: string[];
1058
+ };
1059
+ limits: {
1060
+ max_effort: string | null;
1061
+ max_tool_iterations: number | null;
1062
+ max_context_tokens: number | null;
1063
+ };
819
1064
  }
820
- export interface SourceOut {
1065
+ /** A URL that starts a turn when something happens in another system. */
1066
+ export interface Trigger {
821
1067
  id: string;
822
1068
  name: string;
823
- connector_type: ConnectorType;
824
- mode: "sync" | "query";
825
- /** Config key names only secret values are never returned. */
826
- config_keys: string[];
827
- sync_interval_seconds: number;
828
- tags: string[];
1069
+ prompt: string;
1070
+ enabled: boolean;
1071
+ /** Events that started a run. A sender's retry of the same event is not counted twice. */
1072
+ fired_count: number;
1073
+ last_fired_at: string | null;
1074
+ last_error: string | null;
1075
+ /** Absolute, and give it to the other system. Not shown-once: their configuration holds
1076
+ * it, so we cannot forget it on their behalf. */
1077
+ url: string;
1078
+ /** Only on create and rotate. Sign the body with it and the URL stops being a bearer
1079
+ * token: `X-Signature: sha256=<hmac>`. */
1080
+ secret?: string | null;
1081
+ }
1082
+ /** One memory or wiki page. */
1083
+ export interface KnowledgeItem {
1084
+ id: string;
1085
+ kind: "memory" | "wiki" | string;
1086
+ title?: string | null;
1087
+ text: string;
1088
+ owner_subject?: string | null;
829
1089
  visibility: string;
830
- visibility_scope: string | null;
831
- status: string;
832
- error: string | null;
833
- record_count: number;
834
- last_synced_at: string | null;
835
- created_at: string;
1090
+ /** Documents a wiki page was written from. */
1091
+ source_document_ids: string[];
1092
+ written_at?: string | null;
1093
+ /** True once a source document changed or went away — the page is unverified. */
1094
+ stale?: boolean;
836
1095
  }
837
1096
  export interface AuditEntry {
838
1097
  id: string;
@@ -960,6 +1219,19 @@ export type StreamEvent = {
960
1219
  questions: PendingQuestions[];
961
1220
  };
962
1221
  }
1222
+ /** The agent is waiting to be allowed to do something. Render it and answer — nothing
1223
+ * proceeds until you do. */
1224
+ | {
1225
+ event: "approvals";
1226
+ data: {
1227
+ approvals: PendingApproval[];
1228
+ };
1229
+ }
1230
+ /** The agent drew something. Arrives the instant it is called, mid-turn. */
1231
+ | {
1232
+ event: "ui";
1233
+ data: UiRender;
1234
+ }
963
1235
  /** The agent has handed a page to the user — open a viewer, poll `browserFrame`
964
1236
  * and relay clicks with `browserInput`. It then asks a question and waits. */
965
1237
  | {
@@ -991,12 +1263,16 @@ export type StreamEvent = {
991
1263
  requires_action: boolean;
992
1264
  tool_calls: PendingToolCall[];
993
1265
  questions: PendingQuestions[];
1266
+ approvals: PendingApproval[];
1267
+ ui: UiRender[];
994
1268
  todos: TodoItem[];
995
1269
  subagents: SubagentState[];
996
1270
  handoff?: Handoff | null;
997
1271
  guard_flags: string[];
998
1272
  context?: ContextReport | null;
999
1273
  citations: Citation[];
1274
+ claims: Claim[];
1275
+ attribution: "per-claim" | "retrieval-only" | "none";
1000
1276
  sources: Source[];
1001
1277
  attachments: Attachment[];
1002
1278
  reasoning?: string;
@@ -1046,6 +1322,10 @@ export interface StreamHandlers {
1046
1322
  * cannot leave the panel wrong. One that is `running` after the turn ends is still
1047
1323
  * running — keep showing it, and it will report into the next turn. */
1048
1324
  onSubagents?: (subagents: SubagentState[]) => void;
1325
+ /** UI components the agent may draw, for this stream. Their `render` is called for
1326
+ * you as each arrives; this fires alongside if you also want to observe them. */
1327
+ ui?: UiComponent<any>[];
1328
+ onUi?: (render: UiRender) => void;
1049
1329
  /** A sandbox command's output as it is produced. Append it to a live pane keyed by
1050
1330
  * `command_id` — a long build should look like it is working, and a person watching
1051
1331
  * needs to be able to tell a slow command from a stuck one. */
@@ -1075,6 +1355,9 @@ export interface StreamHandlers {
1075
1355
  * picker, resolve with what the user chose (or `{ chat_instead: true }` if they
1076
1356
  * would rather keep talking), and `done` resolves only once the agent finishes. */
1077
1357
  onQuestion?: QuestionHandler;
1358
+ /** Decide the agent's approval requests and continue the stream. Without it the
1359
+ * stream ends with `approvals` on `done` and you resume it yourself. */
1360
+ onApproval?: ApprovalHandler;
1078
1361
  /** Called each time the client (re)connects, with the resume attempt count. */
1079
1362
  onReconnect?: (attempt: number) => void;
1080
1363
  signal?: AbortSignal;
@@ -1118,6 +1401,10 @@ export interface ChatDone {
1118
1401
  tool_calls: PendingToolCall[];
1119
1402
  /** Questions the agent paused on. Empty once `onQuestion` has answered them. */
1120
1403
  questions: PendingQuestions[];
1404
+ /** Approvals the agent is waiting on. Empty once `onApproval` has decided them. */
1405
+ approvals: PendingApproval[];
1406
+ /** Components the agent drew this turn, in call order. Already rendered by then. */
1407
+ ui: UiRender[];
1121
1408
  /** The agent's plan for this session, when the todo family ran this turn. */
1122
1409
  todos: TodoItem[];
1123
1410
  /** Every subagent of the conversation. Some may still be `running` — the turn
@@ -1130,6 +1417,13 @@ export interface ChatDone {
1130
1417
  /** Set only when history had to be trimmed or summarized to fit the window. */
1131
1418
  context?: ContextReport | null;
1132
1419
  citations: Citation[];
1420
+ /** Sentences attributed to a passage, in order. Empty when the model emitted no markers,
1421
+ * which `attribution` reports rather than hiding. */
1422
+ claims: Claim[];
1423
+ /** `per-claim` · `retrieval-only` · `none`. Draw footnotes from `claims` on the first;
1424
+ * on the second you have the retrieval set and no attribution, which is a different
1425
+ * thing to show. */
1426
+ attribution: "per-claim" | "retrieval-only" | "none";
1133
1427
  sources: Source[];
1134
1428
  attachments: Attachment[];
1135
1429
  /** The full thinking trace of a reasoning model, else "" (streamed as `reasoning`). */
@@ -1208,6 +1502,7 @@ export declare class AgentFramework {
1208
1502
  private readonly opts;
1209
1503
  private readonly _fetch;
1210
1504
  private readonly toolRegistry;
1505
+ private readonly uiRegistry;
1211
1506
  /** Active session watchers, so a streamed turn can mark its own messages seen. */
1212
1507
  private readonly watchers;
1213
1508
  /** The bearer in use: `opts.token` initially, replaced on refresh. */
@@ -1253,8 +1548,20 @@ export declare class AgentFramework {
1253
1548
  /** Register a client-side tool (handler run when the agent calls it). */
1254
1549
  registerTool(tool: ClientToolDef): this;
1255
1550
  registerTools(tools: ClientToolDef[]): this;
1551
+ /** Register a UI component the agent can draw into your app.
1552
+ *
1553
+ * Unlike a tool, nothing is handed back: the agent calls it, your `render` runs, and
1554
+ * the turn carries on without waiting. */
1555
+ registerUi(component: UiComponent<any>): this;
1556
+ registerUiComponents(components: UiComponent<any>[]): this;
1256
1557
  /** Merge the client-level registry with any per-call tools (per-call wins). */
1257
1558
  private resolveTools;
1559
+ private resolveUi;
1560
+ /** Draw whatever the agent asked for, in order.
1561
+ *
1562
+ * A component that throws is logged and skipped: one broken chart must not take down
1563
+ * the turn that drew it, and there is nothing to report back to the agent anyway. */
1564
+ private renderUi;
1258
1565
  /** `bearer` overrides token resolution — used to replay a request with the token a
1259
1566
  * refresh just produced, instead of asking for one again. */
1260
1567
  private authHeaders;
@@ -1448,6 +1755,42 @@ export declare class AgentFramework {
1448
1755
  }) => Promise<SessionOut>;
1449
1756
  };
1450
1757
  };
1758
+ /** Agent Plugins — the skills this token can reach for, and the ones it may add.
1759
+ *
1760
+ * Two sources, one list: what the project published plus anything this end-user
1761
+ * uploaded. Uploading needs the `plugins:write` capability; reading does not, because
1762
+ * a project that publishes a procedure wants its agent to use it. */
1763
+ plugins: {
1764
+ list: () => Promise<PublishedPlugin[]>;
1765
+ delete: (id: string) => Promise<void>;
1766
+ /** Publish a skill. Re-uploading a name replaces it.
1767
+ *
1768
+ * Takes whatever the customer actually has:
1769
+ *
1770
+ * - a packaged Agent Plugin (`plugin.json` + `skills/`), read as-is;
1771
+ * - a zipped folder of skills, or a single `SKILL.md` — a manifest is written
1772
+ * for them, because requiring one to publish a file of instructions is a
1773
+ * packaging exercise standing in front of the feature;
1774
+ * - a folder's files, from a directory picker, each keyed by its relative path.
1775
+ *
1776
+ * An end-user's plugin is private to them and unioned on top of the project's —
1777
+ * only a project key can publish to everyone. */
1778
+ upload: (file: UploadInput, opts?: {
1779
+ filename?: string;
1780
+ signal?: AbortSignal;
1781
+ }) => Promise<PublishedPlugin>;
1782
+ /** Publish a folder of skills without zipping it.
1783
+ *
1784
+ * `files` is what a browser directory picker gives you. Each part is sent under
1785
+ * its path relative to the folder, which is all the server needs to lay the
1786
+ * skills out — so no zip library is needed on your side. */
1787
+ uploadFolder: (files: {
1788
+ path: string;
1789
+ content: UploadInput;
1790
+ }[], opts?: {
1791
+ signal?: AbortSignal;
1792
+ }) => Promise<PublishedPlugin>;
1793
+ };
1451
1794
  documents: {
1452
1795
  list: (query?: {
1453
1796
  tag?: string;
@@ -1509,26 +1852,65 @@ export declare class AgentFramework {
1509
1852
  get: (id: string) => Promise<TaskOut>;
1510
1853
  cancel: (id: string) => Promise<TaskOut>;
1511
1854
  };
1512
- sources: {
1513
- list: () => Promise<SourceOut[]>;
1514
- get: (id: string) => Promise<SourceOut>;
1515
- types: () => Promise<{
1516
- types: string[];
1517
- }>;
1518
- /** Rotate credentials, retag, change the refresh cadence or the ACL. A new
1519
- * `config` is tested before it replaces the old one, so a bad DSN fails here
1520
- * (400) instead of at the next scheduled sync. Changing the interval
1521
- * reschedules; 0 turns auto-refresh off. */
1522
- patch: (id: string, body: AclFields & {
1855
+ /**
1856
+ * Turns that start because something happened somewhere else.
1857
+ *
1858
+ * A trigger is a URL you give another system — a ticket tracker, a CI job, a Zapier step
1859
+ * — and the path is the credential, so it runs as a fixed subject chosen at creation.
1860
+ * These were the only routes in the API reference with no SDK method: every integration
1861
+ * hand-wrote `fetch` for them.
1862
+ *
1863
+ * The URL comes back absolute and is not a secret we can show once: the whole point is
1864
+ * that someone else's configuration holds it. `secret` IS shown once — with it, the
1865
+ * sender signs the body and the URL stops being a bearer token.
1866
+ */
1867
+ triggers: {
1868
+ list: () => Promise<Trigger[]>;
1869
+ /** `prompt` may interpolate the event: `"A ticket arrived: {{ body.title }}"`. */
1870
+ create: (opts: {
1871
+ prompt: string;
1523
1872
  name?: string;
1524
- config?: Record<string, unknown>;
1525
- sync_interval_seconds?: number;
1526
- tags?: string[];
1527
- }) => Promise<SourceOut>;
1528
- /** Trigger a sync now; pass `fullRefresh` to re-pull everything. */
1529
- sync: (id: string, fullRefresh?: boolean) => Promise<SourceOut>;
1530
- remove: (id: string) => Promise<void>;
1873
+ systemPrompt?: string;
1874
+ /** Run every event in one conversation. Off by default: two unrelated events sharing
1875
+ * a transcript confuse both. */
1876
+ sessionId?: string;
1877
+ /** Require a signed body, and return the key to sign it with. */
1878
+ signed?: boolean;
1879
+ }) => Promise<Trigger>;
1880
+ delete: (triggerId: string) => Promise<void>;
1881
+ /** A new URL, with the old one alive for 24 hours — so telling the other system its new
1882
+ * address is not an outage. */
1883
+ rotate: (triggerId: string) => Promise<Trigger>;
1531
1884
  };
1885
+ /**
1886
+ * What the agent wrote down — remembered facts and wiki pages.
1887
+ *
1888
+ * Retrieval reads these back on every turn, so when an answer looks wrong a
1889
+ * remembered fact is often the reason. The ACL that governs retrieval governs this
1890
+ * too: a caller sees exactly what its token could have retrieved.
1891
+ */
1892
+ memory: {
1893
+ /** `kind` is "memory" (facts, per end-user) or "wiki" (pages, per user or shared). */
1894
+ list: (opts?: {
1895
+ kind?: "memory" | "wiki";
1896
+ limit?: number;
1897
+ }) => Promise<KnowledgeItem[]>;
1898
+ /** Forget one. The agent can write it again; this removes what is there now. */
1899
+ delete: (itemId: string) => Promise<void>;
1900
+ };
1901
+ /**
1902
+ * What this token can actually do.
1903
+ *
1904
+ * The three gates on a turn are the platform's kill switch, the token's capability
1905
+ * and the per-request `enable_*` flag, and until this existed a client could read
1906
+ * none of them. `flags[].effective` is the useful one: false means setting that flag
1907
+ * changes nothing on this token — which is otherwise indistinguishable from the agent
1908
+ * simply choosing not to use the tool.
1909
+ *
1910
+ * Cheap and safe to call on load: a token asking what it holds is reading its own
1911
+ * claims back, so it needs no capability of its own.
1912
+ */
1913
+ capabilities: () => Promise<TokenCapabilities>;
1532
1914
  audit: {
1533
1915
  /** Read the tenant's audit trail (admin). Filter by action/subject. */
1534
1916
  list: (opts?: {
@@ -1588,16 +1970,6 @@ export declare class AgentFramework {
1588
1970
  /** Pull a file out of the sandbox as bytes. */
1589
1971
  download: (id: string, path: string) => Promise<Blob>;
1590
1972
  };
1591
- /**
1592
- * Connect an external data source and keep it live in the agent's knowledge.
1593
- * Validates the connector, runs an initial sync, and (when an interval is set)
1594
- * schedules recurring auto-refresh so the data never goes stale.
1595
- *
1596
- * await ai.sync({ name: "orders", connector_type: "postgres",
1597
- * config: { dsn, query: "select id, status, total from orders",
1598
- * cursor_column: "updated_at" }, sync_interval_seconds: 900 });
1599
- */
1600
- sync: (config: SyncConfig) => Promise<SourceOut>;
1601
1973
  private runWithTools;
1602
1974
  /** Wrap startStream so a `done` carrying `requires_action` auto-executes the
1603
1975
  * client tools and continues the stream (same session) until the agent ends. */
@@ -1608,6 +1980,321 @@ export declare class AgentFramework {
1608
1980
  private multipartUpload;
1609
1981
  private rangedDownload;
1610
1982
  }
1983
+ /** Where projects are administered and end-user tokens are minted. A different host
1984
+ * from {@link DEFAULT_BASE_URL}, and a different credential — the two are not
1985
+ * interchangeable, which is the whole reason there are two clients. */
1986
+ export declare const DEFAULT_CONTROL_PLANE_URL = "https://oberik.com";
1987
+ export interface ProjectClientOptions {
1988
+ /** From the project's URL in the dashboard. */
1989
+ projectId: string;
1990
+ /** `pk_…`. Server-side only — see the constructor. */
1991
+ projectKey: string;
1992
+ /** Only for a self-hosted control plane. */
1993
+ baseUrl?: string;
1994
+ fetch?: typeof fetch;
1995
+ }
1996
+ export interface MintTokenInput {
1997
+ /** Who this token is. Owns whatever it creates, and is the default visibility
1998
+ * boundary. A hierarchical path: `acme:finance:ana`. */
1999
+ subject: string;
2000
+ /** What it may SEE — a prefix of `subject`. Omit for "only its own data". */
2001
+ scope?: string;
2002
+ /** What it may DO. Intersected with the project's ceiling: you can narrow, never
2003
+ * widen. Omit and the token carries everything the project allows. */
2004
+ capabilities?: string[];
2005
+ roles?: string[];
2006
+ groups?: string[];
2007
+ /** Restrict this token to a subset of the project's models. */
2008
+ models?: string[];
2009
+ maxEffort?: "minimal" | "low" | "medium" | "high";
2010
+ /** Ceiling on agent↔tool loops. Unset means unlimited. */
2011
+ maxToolIterations?: number;
2012
+ maxContextTokens?: number;
2013
+ /** Seconds. Keep it short and mint per session. */
2014
+ expiresIn?: number;
2015
+ }
2016
+ export interface MintedToken {
2017
+ access_token: string;
2018
+ token_type: string;
2019
+ expires_in: number;
2020
+ tenant_id: string;
2021
+ /** What was ACTUALLY granted. Compare with what you asked for to see what the
2022
+ * project's ceiling trimmed — a capability you expected and did not get is a
2023
+ * toggle in the dashboard, not a bug in your code. */
2024
+ capabilities: string[];
2025
+ scope: string | null;
2026
+ allowed_models: string[] | null;
2027
+ max_effort: string | null;
2028
+ max_context_tokens: number | null;
2029
+ }
2030
+ /**
2031
+ * The control plane, from your backend.
2032
+ *
2033
+ * The other client in this package talks to the data plane as one end-user. This one
2034
+ * holds the project key and administers the project itself: minting those tokens,
2035
+ * setting the capability ceiling, curating the corpus.
2036
+ *
2037
+ * They are separate classes on purpose. A project key can mint a token with any
2038
+ * capability the project allows — and create further keys, and delete the project — so
2039
+ * it must never travel to the same place an end-user token does. Two types make that a
2040
+ * decision someone has to make rather than a field they can accidentally set.
2041
+ */
2042
+ export declare class OberikProject {
2043
+ readonly baseUrl: string;
2044
+ private readonly projectId;
2045
+ private readonly key;
2046
+ private readonly _fetch;
2047
+ constructor(opts: ProjectClientOptions);
2048
+ private request;
2049
+ tokens: {
2050
+ /**
2051
+ * Mint a short-lived token for ONE end-user.
2052
+ *
2053
+ * const { access_token } = await oberik.tokens.mint({
2054
+ * subject: `${user.orgId}:${user.id}`,
2055
+ * scope: `${user.orgId}:${user.id}`,
2056
+ * capabilities: ["chat", "documents:read"],
2057
+ * });
2058
+ */
2059
+ mint: (input: MintTokenInput) => Promise<MintedToken>;
2060
+ /**
2061
+ * The same thing shaped as the callback {@link createClient} wants, so the two
2062
+ * halves of this package fit together without a wrapper:
2063
+ *
2064
+ * const ai = createClient({ getToken: oberik.tokens.forUser({ subject: id }) });
2065
+ *
2066
+ * Called again whenever a token expires, so the client refreshes on its own.
2067
+ */
2068
+ forUser: (input: MintTokenInput) => () => Promise<string>;
2069
+ };
2070
+ /** The capability ceiling: the maximum any token minted here may hold. */
2071
+ capabilities: {
2072
+ get: () => Promise<Record<string, unknown>>;
2073
+ /** Merges — send only what you want to change. */
2074
+ set: (caps: Record<string, unknown>) => Promise<unknown>;
2075
+ };
2076
+ /** Documents owned by the project rather than by any one end-user: the corpus you
2077
+ * curate and your users only read. */
2078
+ /**
2079
+ * The corpus you curate.
2080
+ *
2081
+ * Uploads here are stored `tenant`-visible — readable by every end-user of the project —
2082
+ * which is what "a corpus you curate, that users only read" means. That is the default
2083
+ * and the only option, deliberately: a project key is not a person, so there is no
2084
+ * per-user subtree for it to write into. Per-user documents go through the data-plane
2085
+ * client with an end-user token, where the subject IS the owner.
2086
+ *
2087
+ * The docs used to show `visibility: "tenant"` being passed here, which was neither
2088
+ * accepted nor needed — a recipe that worked by luck rather than by expression.
2089
+ */
2090
+ documents: {
2091
+ list: () => Promise<unknown[]>;
2092
+ upload: (file: Blob | File, opts?: {
2093
+ filename?: string;
2094
+ tags?: string[];
2095
+ }) => Promise<unknown>;
2096
+ delete: (documentId: string) => Promise<void>;
2097
+ };
2098
+ /**
2099
+ * The models this project runs on, and the retrieval it uses.
2100
+ *
2101
+ * These had no methods at all: `project-api.md` documents them in a table and every
2102
+ * integration hand-rolled `fetch` for half its setup. `providers.add` is the one that
2103
+ * matters most — it is the step a new project cannot answer a question without, and it
2104
+ * finishes the rest of the setup itself (see `derived` in the response).
2105
+ *
2106
+ * `providers.catalog` is deliberately absent: it is not project-scoped, so it does not
2107
+ * belong on a client whose every path hangs off one project.
2108
+ */
2109
+ providers: {
2110
+ list: () => Promise<unknown[]>;
2111
+ /** Name a chat model AND an embedding model: the first lets the agent answer, the
2112
+ * second lets it index. The response's `derived` says what was set for you. */
2113
+ add: (opts: {
2114
+ provider: string;
2115
+ models: string[];
2116
+ values: Record<string, string>;
2117
+ label?: string;
2118
+ }) => Promise<{
2119
+ id: string;
2120
+ derived?: Record<string, unknown>;
2121
+ }>;
2122
+ edit: (credId: string, opts: {
2123
+ models?: string[];
2124
+ label?: string;
2125
+ values?: Record<string, string>;
2126
+ }) => Promise<unknown>;
2127
+ /** Re-read the provider's catalog: a model registered before its price was published
2128
+ * bills nothing, so the usage cap never trips. */
2129
+ refresh: (credId: string) => Promise<unknown>;
2130
+ remove: (credId: string) => Promise<void>;
2131
+ };
2132
+ /** The model used when a request does not name one. */
2133
+ defaultModel: {
2134
+ set: (model: string) => Promise<unknown>;
2135
+ };
2136
+ /** Embedding and rerank overrides. Set for you when you register an embedding model, so
2137
+ * this is for changing it rather than for getting started. */
2138
+ retrieval: {
2139
+ set: (opts: {
2140
+ embeddingModel?: string;
2141
+ embeddingDim?: number;
2142
+ rerankModel?: string;
2143
+ }) => Promise<unknown>;
2144
+ /** How many floats a model returns, measured by embedding one word. No provider
2145
+ * publishes it, and a wrong one fails at the first ingest rather than here. */
2146
+ probe: (model: string) => Promise<{
2147
+ dim: number | null;
2148
+ error?: string;
2149
+ }>;
2150
+ };
2151
+ /** How documents are read: the built-in parser, or a vision model you choose. */
2152
+ documentProcessor: {
2153
+ set: (opts: {
2154
+ type: string;
2155
+ model?: string;
2156
+ }) => Promise<unknown>;
2157
+ };
2158
+ /** What happens when a conversation outgrows the model's window. */
2159
+ context: {
2160
+ get: () => Promise<unknown>;
2161
+ set: (opts: Record<string, unknown>) => Promise<unknown>;
2162
+ };
2163
+ /** Spend and rate caps on this project's LLM key. Enforced by the biller, so they hold
2164
+ * even when the usage views cannot be read. */
2165
+ limits: {
2166
+ get: () => Promise<unknown>;
2167
+ set: (opts: {
2168
+ maxBudget?: number | null;
2169
+ budgetDuration?: string;
2170
+ tpmLimit?: number | null;
2171
+ rpmLimit?: number | null;
2172
+ }) => Promise<unknown>;
2173
+ };
2174
+ /** Which models a delegate may run on, and how many may run at once. Without this the
2175
+ * subagents capability stays unavailable however it is granted. */
2176
+ subagents: {
2177
+ set: (opts: {
2178
+ models: string[];
2179
+ maxConcurrent?: number;
2180
+ }) => Promise<unknown>;
2181
+ };
2182
+ /** Procedures you publish as Agent Plugins, and what your end-users have added. */
2183
+ skills: {
2184
+ list: () => Promise<unknown[]>;
2185
+ upload: (zip: Blob | File, filename?: string) => Promise<unknown>;
2186
+ delete: (pluginId: string) => Promise<void>;
2187
+ };
2188
+ /** MCP servers whose tools join this project's catalog. */
2189
+ mcp: {
2190
+ list: () => Promise<unknown[]>;
2191
+ add: (opts: {
2192
+ name: string;
2193
+ url: string;
2194
+ transport?: "streamable_http" | "sse";
2195
+ headers?: Record<string, string>;
2196
+ }) => Promise<unknown>;
2197
+ remove: (mcpId: string) => Promise<void>;
2198
+ };
2199
+ /** Conversations, and what was said in them. */
2200
+ sessions: {
2201
+ list: () => Promise<unknown[]>;
2202
+ messages: (sessionId: string) => Promise<unknown[]>;
2203
+ };
2204
+ /** Scheduled work this project's end-users have created. */
2205
+ tasks: {
2206
+ list: () => Promise<unknown[]>;
2207
+ };
2208
+ /** What the agent has written down: remembered facts and wiki pages. */
2209
+ wiki: {
2210
+ list: () => Promise<unknown[]>;
2211
+ delete: (itemId: string) => Promise<void>;
2212
+ };
2213
+ /** Live sandboxes, and what to do about one. */
2214
+ sandboxes: {
2215
+ list: () => Promise<unknown[]>;
2216
+ action: (sessionId: string, action: "pause" | "resume" | "delete") => Promise<unknown>;
2217
+ };
2218
+ /** Prepended to every request for this project, above anything a caller sends. */
2219
+ systemPrompt: {
2220
+ set: (systemPrompt: string) => Promise<unknown>;
2221
+ };
2222
+ /** Browser origins allowed to call the data plane with this project's tokens. */
2223
+ origins: {
2224
+ set: (origins: string[]) => Promise<unknown>;
2225
+ };
2226
+ /** Tools the agent calls by URL. The signing secret comes back once, on create. */
2227
+ webhookTools: {
2228
+ list: () => Promise<unknown[]>;
2229
+ create: (tool: {
2230
+ name: string;
2231
+ description: string;
2232
+ url: string;
2233
+ parameters?: Record<string, unknown>;
2234
+ headers?: Record<string, string>;
2235
+ }) => Promise<{
2236
+ id: string;
2237
+ secret: string;
2238
+ }>;
2239
+ delete: (toolId: string) => Promise<void>;
2240
+ };
2241
+ /** Server-side keys. A created one is returned once and never again. */
2242
+ /**
2243
+ * Checks on what goes into the model and what comes back.
2244
+ *
2245
+ * The enforcement has existed for a long time and there was no way to configure it — no
2246
+ * route, no dashboard section, no column — so a documentation page described switches
2247
+ * that could not be reached. `set` takes a partial: what you do not mention is left as it
2248
+ * is.
2249
+ */
2250
+ guardrails: {
2251
+ get: () => Promise<GuardrailPolicy>;
2252
+ set: (policy: GuardrailUpdate) => Promise<GuardrailPolicy>;
2253
+ };
2254
+ /**
2255
+ * Whether this project can actually answer a question yet.
2256
+ *
2257
+ * A new project has no models, so it can neither answer nor index anything — and the
2258
+ * flag that used to be the closest thing to this (`hasLlm`) meant "a LiteLLM key was
2259
+ * provisioned", which is true from the moment a project exists. Every unfinished step
2260
+ * names what it blocks and the one call that fixes it.
2261
+ *
2262
+ * Worth calling in a deploy check: a project that is not ready fails every request with
2263
+ * the provider's own error, which reads as your bug rather than as missing setup.
2264
+ */
2265
+ readiness: () => Promise<ProjectReadiness>;
2266
+ /** The starting snippet and this project's endpoints — the same one the dashboard and the
2267
+ * SSH gateway show, so there is one of it rather than three. */
2268
+ connect: () => Promise<ConnectInfo>;
2269
+ /**
2270
+ * Further project keys.
2271
+ *
2272
+ * `scope` is the important argument and it defaults to the narrow one. A `mint` key
2273
+ * can turn your signed-in user into an end-user token and nothing else — it cannot
2274
+ * read the corpus, raise the capability ceiling, issue more keys, or delete the
2275
+ * project. That is what almost every backend actually needs, and it is the difference
2276
+ * between a leaked key costing you some tokens and costing you the workspace.
2277
+ */
2278
+ keys: {
2279
+ list: () => Promise<ProjectKeyInfo[]>;
2280
+ create: (name: string, opts?: {
2281
+ scope?: ProjectKeyScope;
2282
+ }) => Promise<{
2283
+ id: string;
2284
+ key: string;
2285
+ scope: ProjectKeyScope;
2286
+ }>;
2287
+ revoke: (keyId: string) => Promise<void>;
2288
+ };
2289
+ /** Spend, requests, tokens and latency — including per end-user, since spend is
2290
+ * attributed to the token's subject. */
2291
+ usage: {
2292
+ summary: () => Promise<unknown>;
2293
+ observability: (windowSeconds?: number) => Promise<unknown>;
2294
+ };
2295
+ }
2296
+ /** Factory helper for the server-side client. */
2297
+ export declare function createProjectClient(opts: ProjectClientOptions): OberikProject;
1611
2298
  /** Factory helper. */
1612
2299
  export declare function createClient(opts: ClientOptions): AgentFramework;
1613
2300
  export default AgentFramework;