@introspection-sdk/introspection-browser 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/api/client.ts DELETED
@@ -1,196 +0,0 @@
1
- /**
2
- * Browser-side Introspection API client.
3
- *
4
- * Lets a single-page app create and stream Introspection tasks directly
5
- * from the browser, with no API key in JavaScript. The browser talks only
6
- * to the Data Plane — runtime resolution and any other Control Plane work
7
- * stays on the app's backend, so the CP never needs to serve CORS to
8
- * customer web origins. The auth boundary:
9
- *
10
- * 1. The SPA's own backend ("broker") mints a short-lived Introspection
11
- * access token — via RFC 8693 token-exchange of the partner IdP
12
- * token, a PKCE `authorization_code`, or `client_credentials` (the
13
- * IdP secret never leaves the backend) — and, when a specific runtime
14
- * is needed, resolves its `runtime_id` server-side. The SPA fetches
15
- * the token through the `getToken` callback.
16
- * 2. `connect()` redeems the token at the DP `POST /v1/oauth/exchange`
17
- * for an HttpOnly session cookie, named for the token's environment lane
18
- * (`intro_dp_development` / `intro_dp_staging` / `intro_dp_production`).
19
- * Distinct names mean an app running several lanes holds a live session
20
- * for each in one browser instead of the newest evicting the last.
21
- * 3. Every subsequent call rides that cookie (`credentials: "include"`)
22
- * — `client.tasks.start({ runtime_id })`, `.get(...)`, run streaming,
23
- * etc.
24
- *
25
- * When the session cookie expires, an in-flight request gets a 401, and
26
- * the client transparently re-runs `getToken` + the DP exchange once
27
- * before retrying.
28
- */
29
-
30
- import { BrowserHttpClient, stripTrailingSlash, toApiError } from "./http.js";
31
- import { resolveBrowserFetch } from "./fetch.js";
32
- import {
33
- ConversationsClient,
34
- FilesClient,
35
- SharesClient,
36
- } from "@introspection-sdk/http";
37
- import type { Environment } from "@introspection-sdk/types";
38
- import { TasksClient } from "./tasks.js";
39
-
40
- type CookieClients = {
41
- tasks: TasksClient;
42
- files: FilesClient;
43
- conversations: ConversationsClient;
44
- shares: SharesClient;
45
- };
46
-
47
- export interface IntrospectionApiClientOptions {
48
- /**
49
- * Data Plane REST base URL for the cookie-session APIs (`connect()` and
50
- * `client.tasks` / `client.files` / `client.conversations` /
51
- * `client.shares`). Required — the browser only talks to the DP.
52
- */
53
- dpUrl: string;
54
- /**
55
- * Returns a fresh Introspection access token from the app's broker
56
- * (its own backend). Called on `connect()` and again whenever the DP
57
- * session cookie needs re-minting after a 401. The session's project is
58
- * derived from this token's claims server-side — there is no separate
59
- * project option.
60
- */
61
- getToken: () => string | Promise<string>;
62
- /** Custom `fetch` (for tests or non-standard runtimes). */
63
- fetch?: typeof fetch;
64
- /** Extra headers merged into every DP request. */
65
- additionalHeaders?: Record<string, string>;
66
- /**
67
- * Environment lane this client talks to.
68
- *
69
- * Normally omit it. `connect()` learns the lane from the exchange response,
70
- * which the DP derives from the token's own `environment` claim — that is the
71
- * authoritative source, and it is why a client cannot simply declare a lane
72
- * it has no token for.
73
- *
74
- * Set it only to send the lane header before the first `connect()` resolves.
75
- * If it disagrees with the token, `connect()` throws rather than silently
76
- * preferring one.
77
- */
78
- environment?: Environment;
79
- }
80
-
81
- export class IntrospectionApiClient {
82
- private readonly fetchImpl: typeof fetch;
83
- private readonly cookieClients: CookieClients;
84
- /**
85
- * Lane established by the most recent exchange. Per instance, never module
86
- * scope — two clients on one page holding different lanes is the point.
87
- */
88
- private resolvedEnvironment?: Environment;
89
-
90
- constructor(private readonly opts: IntrospectionApiClientOptions) {
91
- // Native browser `fetch` throws "Illegal invocation" when called as a
92
- // method of this client (`this.fetchImpl(...)`); resolveBrowserFetch
93
- // returns a global-safe wrapper. The same impl backs the connect()
94
- // exchange here and the cookie-session resource calls via BrowserHttpClient.
95
- this.fetchImpl = resolveBrowserFetch(opts.fetch);
96
- if (!opts.dpUrl) {
97
- throw new Error("IntrospectionApiClient requires a dpUrl");
98
- }
99
- this.resolvedEnvironment = opts.environment;
100
- const http = new BrowserHttpClient({
101
- apiUrl: opts.dpUrl,
102
- additionalHeaders: opts.additionalHeaders,
103
- fetch: this.fetchImpl,
104
- onUnauthorized: () => this.reexchange(),
105
- environment: () => this.resolvedEnvironment,
106
- });
107
- this.cookieClients = {
108
- tasks: new TasksClient(http),
109
- files: new FilesClient(http),
110
- conversations: new ConversationsClient(http),
111
- shares: new SharesClient(http),
112
- };
113
- }
114
-
115
- /** `/v1/tasks` operations bound to the DP session cookie. */
116
- get tasks(): TasksClient {
117
- return this.cookieClients.tasks;
118
- }
119
-
120
- /** `/v1/files` operations bound to the DP session cookie. */
121
- get files(): FilesClient {
122
- return this.cookieClients.files;
123
- }
124
-
125
- /** Read-only `/v1/conversations` projection bound to the session cookie. */
126
- get conversations(): ConversationsClient {
127
- return this.cookieClients.conversations;
128
- }
129
-
130
- /** `/v1/shares` read-sharing grants bound to the session cookie. */
131
- get shares(): SharesClient {
132
- return this.cookieClients.shares;
133
- }
134
-
135
- /**
136
- * Environment lane this client is connected to, once `connect()` has
137
- * resolved. `undefined` before the first exchange, unless the constructor
138
- * was given one.
139
- */
140
- get environment(): Environment | undefined {
141
- return this.resolvedEnvironment;
142
- }
143
-
144
- /**
145
- * Mint a token via `getToken` and redeem it at the DP for the session
146
- * cookie. Call once before issuing task requests.
147
- */
148
- async connect(): Promise<void> {
149
- await this.exchange();
150
- }
151
-
152
- private async exchange(): Promise<void> {
153
- const token = await this.opts.getToken();
154
- const res = await this.fetchImpl(
155
- `${stripTrailingSlash(this.opts.dpUrl)}/v1/oauth/exchange`,
156
- {
157
- method: "POST",
158
- headers: {
159
- "Content-Type": "application/json",
160
- ...(this.opts.additionalHeaders ?? {}),
161
- },
162
- body: JSON.stringify({ token }),
163
- credentials: "include",
164
- },
165
- );
166
- if (!res.ok) throw await toApiError(res);
167
-
168
- // The DP echoes the lane it resolved from the token's claim, so the client
169
- // learns which cookie it just established without being told. Re-read on
170
- // every exchange, including the 401 recovery path: if the broker has since
171
- // switched lanes, a stale value would name a cookie we no longer hold.
172
- const body = (await res.json().catch(() => ({}))) as {
173
- environment?: Environment;
174
- };
175
- if (body.environment) {
176
- if (this.opts.environment && this.opts.environment !== body.environment) {
177
- throw new Error(
178
- `IntrospectionApiClient is configured for environment ` +
179
- `"${this.opts.environment}" but getToken() returned a token for ` +
180
- `"${body.environment}".`,
181
- );
182
- }
183
- this.resolvedEnvironment = body.environment;
184
- }
185
- }
186
-
187
- /** 401 recovery: re-exchange, reporting success so the call can retry. */
188
- private async reexchange(): Promise<boolean> {
189
- try {
190
- await this.exchange();
191
- return true;
192
- } catch {
193
- return false;
194
- }
195
- }
196
- }
package/src/api/fetch.ts DELETED
@@ -1,31 +0,0 @@
1
- /**
2
- * Resolve the `fetch` implementation for browser Data Plane calls.
3
- *
4
- * The native browser `fetch` brand-checks its `this`: it must be the global
5
- * (`window` / `WorkerGlobalScope`). Storing `globalThis.fetch` on an instance
6
- * field and later calling it as a method — `this.fetchImpl(url, init)` — rebinds
7
- * `this` to the client instance, so the browser throws
8
- * `Failed to execute 'fetch' on 'Window': Illegal invocation`.
9
- *
10
- * When no custom `fetch` is supplied we therefore return a thin wrapper that
11
- * invokes `globalThis.fetch(...)` **as a method of the global** (correct `this`)
12
- * and resolves it **per call**. The per-call lookup is deliberate: the only
13
- * reason to go through `globalThis.fetch` rather than the lexical `fetch` is so
14
- * a swapped-in implementation is honored — that is the server-side proxy
15
- * (`@introspection-sdk/introspection-proxy` monkeypatches `globalThis.fetch`),
16
- * never a browser concern. A caller-supplied `fetch` is returned untouched.
17
- *
18
- * This lives in the browser package on purpose: Node's `fetch` (undici) has no
19
- * such brand check, so the shared {@link BaseHttpClient} / Node client keep
20
- * resolving `globalThis.fetch` directly (so the proxy override still applies).
21
- */
22
- export function resolveBrowserFetch(custom?: typeof fetch): typeof fetch {
23
- if (custom) return custom;
24
- if (!globalThis.fetch) {
25
- throw new Error(
26
- "global fetch is unavailable; pass `fetch` or run in a modern browser",
27
- );
28
- }
29
- return (input: RequestInfo | URL, init?: RequestInit) =>
30
- globalThis.fetch(input, init);
31
- }
package/src/api/http.ts DELETED
@@ -1,88 +0,0 @@
1
- /**
2
- * Browser HTTP transport for the Introspection Data Plane `/v1` surface.
3
- *
4
- * The request/response machinery — URL joining, query building, body
5
- * serialization, error mapping, AG-UI stream parsing — lives in the shared
6
- * `@introspection-sdk/http` {@link BaseHttpClient}. This module pins the
7
- * browser auth strategy on top of it: the DP is reached with an HttpOnly
8
- * session cookie minted by `POST /v1/oauth/exchange`, so every request opts
9
- * into `credentials: "include"` and sends NO bearer token — no Introspection
10
- * credential ever lives in JavaScript. A 401 (expired session cookie)
11
- * triggers a single `onUnauthorized` refresh + retry.
12
- *
13
- * The session cookie is named for its environment lane
14
- * (`intro_dp_development` / `intro_dp_staging` / `intro_dp_production`), so an
15
- * app running several lanes holds a live session for each in one browser. When
16
- * more than one is present the DP cannot tell which a request means, so we name
17
- * it in {@link ENVIRONMENT_HEADER}.
18
- */
19
-
20
- import {
21
- BaseHttpClient,
22
- stripTrailingSlash,
23
- toApiError,
24
- } from "@introspection-sdk/http";
25
-
26
- import type { Environment } from "@introspection-sdk/types";
27
-
28
- import { resolveBrowserFetch } from "./fetch.js";
29
-
30
- // Re-exported for `client.ts`, which redeems the DP session on `connect()`.
31
- export { stripTrailingSlash, toApiError };
32
-
33
- /**
34
- * Names which environment lane's session cookie the DP should resolve.
35
- *
36
- * A selector, not a credential: the cookie it names is still validated
37
- * server-side, so naming a lane this browser holds no cookie for resolves to
38
- * nothing rather than granting anything.
39
- */
40
- export const ENVIRONMENT_HEADER = "x-introspection-environment";
41
-
42
- export interface BrowserHttpConfig {
43
- /** DP REST base URL every request is prefixed with. */
44
- apiUrl: string;
45
- /** Extra headers merged into every request. */
46
- additionalHeaders?: Record<string, string>;
47
- /** Custom `fetch` (for tests or non-standard runtimes). */
48
- fetch?: typeof fetch;
49
- /**
50
- * Invoked when a request comes back `401`. Return `true` if the DP
51
- * session was refreshed and the request should be retried once;
52
- * `false` to surface the original error.
53
- */
54
- onUnauthorized?: () => Promise<boolean>;
55
- /**
56
- * Reads the lane established by the most recent exchange.
57
- *
58
- * Called per request rather than captured at construction, because the lane
59
- * is not known until `connect()` resolves — and it can change under a
60
- * re-exchange.
61
- */
62
- environment?: () => Environment | undefined;
63
- }
64
-
65
- /**
66
- * Cookie-authenticated HTTP wrapper bound to one DP endpoint. Has no
67
- * opinion about which resource it serves — the caller picks the path.
68
- */
69
- export class BrowserHttpClient extends BaseHttpClient {
70
- constructor(cfg: BrowserHttpConfig) {
71
- super({
72
- apiUrl: cfg.apiUrl,
73
- // Resolve a browser-safe `fetch` (native `fetch` brand-checks `this`).
74
- fetch: resolveBrowserFetch(cfg.fetch),
75
- additionalHeaders: cfg.additionalHeaders,
76
- transport: {
77
- // Still no bearer token — the HttpOnly cookie is the credential. The
78
- // only per-request contribution is which lane's cookie to resolve.
79
- authHeaders: (): Record<string, string> => {
80
- const environment = cfg.environment?.();
81
- return environment ? { [ENVIRONMENT_HEADER]: environment } : {};
82
- },
83
- credentials: "include",
84
- onUnauthorized: cfg.onUnauthorized,
85
- },
86
- });
87
- }
88
- }
package/src/api/index.ts DELETED
@@ -1,133 +0,0 @@
1
- /**
2
- * Introspection Browser API surface — `@introspection-sdk/introspection-browser/api`.
3
- *
4
- * A cookie-authenticated client for creating and streaming Introspection
5
- * tasks directly from a single-page app, with no API key in the browser.
6
- * Separate from the package's default telemetry export so apps only pull
7
- * in what they use.
8
- *
9
- * The browser talks only to the Data Plane. Resolving a runtime by slug
10
- * (a Control Plane call) stays on your backend, which hands the browser a
11
- * short-lived access token plus the resolved `runtime_id`.
12
- *
13
- * @example
14
- * ```typescript
15
- * import { IntrospectionApiClient } from "@introspection-sdk/introspection-browser/api";
16
- *
17
- * // Your backend returns { token, runtimeId, dpUrl } — it mints the access
18
- * // token, resolves the runtime id, and surfaces the DP URL (e.g. from the
19
- * // Node SDK's serviceAccountToken response), so the browser never calls the CP.
20
- * const { token, runtimeId, dpUrl } = await fetch(
21
- * "/api/introspection/session",
22
- * ).then((r) => r.json());
23
- *
24
- * const client = new IntrospectionApiClient({
25
- * dpUrl,
26
- * getToken: () => token,
27
- * });
28
- *
29
- * await client.connect(); // -> intro_dp_<environment> session cookie
30
- * const run = await client.tasks.start({
31
- * prompt: "Summarize my latest order",
32
- * runtime_id: runtimeId,
33
- * });
34
- * for await (const ev of run.stream()) console.log(ev.type);
35
- * ```
36
- */
37
-
38
- export {
39
- IntrospectionApiClient,
40
- type IntrospectionApiClientOptions,
41
- } from "./client.js";
42
- export {
43
- TasksClient,
44
- TaskRunsClient,
45
- RunHandle,
46
- type CreateTaskParams,
47
- type StartTaskParams,
48
- } from "./tasks.js";
49
- export {
50
- ConversationItemsClient,
51
- ConversationsClient,
52
- FileVersionsClient,
53
- FilesClient,
54
- SharesClient,
55
- type FileUploadBody,
56
- } from "@introspection-sdk/http";
57
- export {
58
- BrowserHttpClient,
59
- ENVIRONMENT_HEADER,
60
- type BrowserHttpConfig,
61
- } from "./http.js";
62
- export {
63
- Paginator,
64
- cursorPaginate,
65
- type PageSource,
66
- } from "@introspection-sdk/http";
67
- export {
68
- TranscriptAccumulator,
69
- foldAgui,
70
- foldSpans,
71
- mergeTranscripts,
72
- } from "@introspection-sdk/http";
73
- export { EventType } from "@introspection-sdk/types";
74
- export {
75
- genAiConversationId,
76
- genAiInputMessages,
77
- genAiOutputMessages,
78
- } from "@introspection-sdk/types";
79
-
80
- // Re-exported wire types for convenience.
81
- export type {
82
- AGUIEvent,
83
- BaseEvent,
84
- Environment,
85
- Interrupt,
86
- Message,
87
- ResumeEntry,
88
- RunAgentInput,
89
- Task,
90
- TaskRun,
91
- TaskStatus,
92
- TaskRunKind,
93
- TaskCreateResponse,
94
- TaskListParams,
95
- TaskUpdateParams,
96
- TaskRunCreateParams,
97
- TaskRunResumeParams,
98
- TaskFileRef,
99
- TaskCancelResponse,
100
- TaskCancelOptions,
101
- RunIdentityInput,
102
- Paginated,
103
- File,
104
- FileType,
105
- FileListParams,
106
- FileUpdateParams,
107
- FileCreateTextParams,
108
- ListParams,
109
- GenAiSpan,
110
- GenAiSpanList,
111
- Conversation,
112
- ConversationAgent,
113
- ConversationUsage,
114
- ConversationCost,
115
- ConversationMetrics,
116
- SpanAttributes,
117
- ConversationSortField,
118
- ConversationListParams,
119
- ConversationItemListParams,
120
- ConversationItemInclude,
121
- ResourceShare,
122
- ShareResourceType,
123
- ShareCreateParams,
124
- ShareListParams,
125
- } from "@introspection-sdk/types";
126
- export {
127
- IntrospectionAPIError,
128
- AuthenticationError,
129
- InsufficientScopeError,
130
- NotFoundError,
131
- NetworkError,
132
- RunnerExpiredError,
133
- } from "@introspection-sdk/types";
package/src/api/tasks.ts DELETED
@@ -1,87 +0,0 @@
1
- import type {
2
- RunIdentityInput,
3
- TaskFileRef,
4
- Uuid,
5
- } from "@introspection-sdk/types";
6
- import {
7
- RunHandle,
8
- TaskRunsClient,
9
- TasksClient as SharedTasksClient,
10
- type ResourceHttpClient,
11
- } from "@introspection-sdk/http";
12
-
13
- /**
14
- * Body for creating a task directly against the DP from the browser.
15
- *
16
- * A browser caller has no pre-pinned `Runner`, so — unlike the Node
17
- * SDK's runner-bound create — it selects the agent itself: pass
18
- * `runtime_id` to pin a recipe runtime, or `agent_name` to fall back to
19
- * a named recipe agent. `identity` is a convenience that is folded into
20
- * `metadata.identity` for attribution (the DP derives the owning
21
- * `identity_key` from the session's JWT claims, never from this body).
22
- */
23
- export interface CreateTaskParams {
24
- prompt?: string;
25
- /** Pin the task to a specific recipe runtime. */
26
- runtime_id?: Uuid;
27
- /** Named recipe agent, when no `runtime_id` is pinned. */
28
- agent_name?: string;
29
- title?: string;
30
- metadata?: Record<string, unknown>;
31
- /**
32
- * Override the interactive idle window (seconds) before the sandbox is
33
- * torn down. `0` tears down as soon as it's provisioned; omit to use the
34
- * deployment default. Clamped to the task timeout.
35
- */
36
- idle_timeout_seconds?: number;
37
- /** Caller identity for attribution; merged into `metadata.identity`. */
38
- identity?: RunIdentityInput;
39
- /**
40
- * Files to attach to this task, uploaded first via `client.files`.
41
- *
42
- * Only for files the user picked before the first turn. To attach one
43
- * mid-conversation, put `files` on the run instead
44
- * (`client.tasks.runs.create(taskId, { prompt, files })`) — the task's
45
- * workspace is built when its sandbox starts, so a later attachment has to
46
- * ride the turn that references it.
47
- */
48
- files?: TaskFileRef[];
49
- /**
50
- * Fork from a shared conversation: the `/v1/shares` grant id for the source
51
- * conversation. Its presence makes this create a fork — the server seeds the
52
- * new task with that conversation's history, read via the share (the
53
- * permissions boundary).
54
- */
55
- fork_share_id?: string;
56
- }
57
-
58
- export interface StartTaskParams extends CreateTaskParams {
59
- /** Required for the `start()` sugar that returns a run handle. */
60
- prompt: string;
61
- }
62
-
63
- function toTaskBody(params: CreateTaskParams): Record<string, unknown> {
64
- const { identity, metadata, ...rest } = params;
65
- const body: Record<string, unknown> = { ...rest };
66
- const mergedMetadata = identity
67
- ? { ...(metadata ?? {}), identity }
68
- : metadata;
69
- if (mergedMetadata !== undefined) body.metadata = mergedMetadata;
70
- return body;
71
- }
72
-
73
- /**
74
- * Cookie-authenticated `/v1/tasks` client. The implementation is shared with
75
- * Node's runner-bound task client; this browser subclass only adds the
76
- * `identity -> metadata.identity` body mapping used by cookie-session calls.
77
- */
78
- export class TasksClient extends SharedTasksClient<
79
- CreateTaskParams,
80
- StartTaskParams
81
- > {
82
- constructor(http: ResourceHttpClient) {
83
- super(http, toTaskBody);
84
- }
85
- }
86
-
87
- export { RunHandle, TaskRunsClient };