@introspection-sdk/introspection-browser 0.14.0 → 0.16.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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @introspection-sdk/introspection-browser@0.14.0 build /home/runner/work/introspection-js-sdk/introspection-js-sdk/packages/introspection-browser
2
+ > @introspection-sdk/introspection-browser@0.16.0 build /home/runner/work/introspection-js-sdk/introspection-js-sdk/packages/introspection-browser
3
3
  > tsc
4
4
 
package/README.md CHANGED
@@ -50,7 +50,8 @@ customer web origins.
50
50
  `client.runtimes.resolve("support-agent")`) and supplies the Data
51
51
  Plane URL, so the SPA needs no Introspection config of its own.
52
52
  2. `client.connect()` redeems the token at the **Data Plane**
53
- `POST /v1/oauth/exchange` for the HttpOnly `intro_dp_session` cookie.
53
+ `POST /v1/oauth/exchange` for an HttpOnly session cookie, named for the
54
+ token's environment lane (see [Environments](#environments)).
54
55
  3. `client.tasks.start({ runtime_id })` and friends ride that cookie against
55
56
  the Data Plane for tasks, files, conversations, and shares.
56
57
 
@@ -69,7 +70,7 @@ const client = new IntrospectionApiClient({
69
70
  getToken: () => token,
70
71
  });
71
72
 
72
- await client.connect(); // -> intro_dp_session cookie
73
+ await client.connect(); // -> intro_dp_<environment> session cookie
73
74
 
74
75
  const run = await client.tasks.start({
75
76
  prompt: "Summarize my latest order",
@@ -94,6 +95,48 @@ as soon as it's provisioned; omit to use the deployment default. Clamped to the
94
95
  task timeout. When no `runtime_id` is supplied, pass `agent_name` to select a
95
96
  named recipe agent instead.
96
97
 
98
+ ### Environments
99
+
100
+ The session cookie is named for its environment lane —
101
+ `intro_dp_development`, `intro_dp_staging`, `intro_dp_production` — so an app
102
+ running more than one lane holds a live session for **each** in one browser. A
103
+ single shared name gave the browser one slot, so a second `connect()` evicted
104
+ the first.
105
+
106
+ **You never pass the environment in.** Your backend picks the lane when it
107
+ mints the token; the Data Plane reads it off the token's claim and returns it,
108
+ and the client sends it back as `x-introspection-environment` so the right
109
+ cookie is resolved:
110
+
111
+ ```typescript
112
+ const dev = new IntrospectionApiClient({
113
+ dpUrl,
114
+ getToken: () => mintToken({ environment: "development" }),
115
+ });
116
+ const prod = new IntrospectionApiClient({
117
+ dpUrl,
118
+ getToken: () => mintToken({ environment: "production" }),
119
+ });
120
+
121
+ await Promise.all([dev.connect(), prod.connect()]);
122
+
123
+ dev.environment; // "development" -> cookie intro_dp_development
124
+ prod.environment; // "production" -> cookie intro_dp_production
125
+
126
+ await dev.tasks.list(); // x-introspection-environment: development
127
+ await prod.tasks.list(); // x-introspection-environment: production
128
+ ```
129
+
130
+ `client.environment` is `undefined` until `connect()` resolves. The optional
131
+ `environment` constructor option sends the header before that first exchange;
132
+ if it disagrees with the token, `connect()` throws rather than silently
133
+ preferring one — the token is the authority.
134
+
135
+ > **Requires a Data Plane that returns `environment` from
136
+ > `/v1/oauth/exchange`.** Against an older deployment `client.environment`
137
+ > stays `undefined`, no header is sent, and a single-lane app keeps working
138
+ > unchanged.
139
+
97
140
  ## Files and conversations
98
141
 
99
142
  The same cookie session also reaches `/v1/files` and (read-only)
@@ -14,7 +14,10 @@
14
14
  * is needed, resolves its `runtime_id` server-side. The SPA fetches
15
15
  * the token through the `getToken` callback.
16
16
  * 2. `connect()` redeems the token at the DP `POST /v1/oauth/exchange`
17
- * for the HttpOnly `intro_dp_session` cookie.
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.
18
21
  * 3. Every subsequent call rides that cookie (`credentials: "include"`)
19
22
  * — `client.tasks.start({ runtime_id })`, `.get(...)`, run streaming,
20
23
  * etc.
@@ -24,6 +27,7 @@
24
27
  * before retrying.
25
28
  */
26
29
  import { ConversationsClient, FilesClient, SharesClient } from "@introspection-sdk/http";
30
+ import type { Environment } from "@introspection-sdk/types";
27
31
  import { TasksClient } from "./tasks.js";
28
32
  export interface IntrospectionApiClientOptions {
29
33
  /**
@@ -44,11 +48,29 @@ export interface IntrospectionApiClientOptions {
44
48
  fetch?: typeof fetch;
45
49
  /** Extra headers merged into every DP request. */
46
50
  additionalHeaders?: Record<string, string>;
51
+ /**
52
+ * Environment lane this client talks to.
53
+ *
54
+ * Normally omit it. `connect()` learns the lane from the exchange response,
55
+ * which the DP derives from the token's own `environment` claim — that is the
56
+ * authoritative source, and it is why a client cannot simply declare a lane
57
+ * it has no token for.
58
+ *
59
+ * Set it only to send the lane header before the first `connect()` resolves.
60
+ * If it disagrees with the token, `connect()` throws rather than silently
61
+ * preferring one.
62
+ */
63
+ environment?: Environment;
47
64
  }
48
65
  export declare class IntrospectionApiClient {
49
66
  private readonly opts;
50
67
  private readonly fetchImpl;
51
68
  private readonly cookieClients;
69
+ /**
70
+ * Lane established by the most recent exchange. Per instance, never module
71
+ * scope — two clients on one page holding different lanes is the point.
72
+ */
73
+ private resolvedEnvironment?;
52
74
  constructor(opts: IntrospectionApiClientOptions);
53
75
  /** `/v1/tasks` operations bound to the DP session cookie. */
54
76
  get tasks(): TasksClient;
@@ -59,8 +81,14 @@ export declare class IntrospectionApiClient {
59
81
  /** `/v1/shares` read-sharing grants bound to the session cookie. */
60
82
  get shares(): SharesClient;
61
83
  /**
62
- * Mint a token via `getToken` and redeem it at the DP for the
63
- * `intro_dp_session` cookie. Call once before issuing task requests.
84
+ * Environment lane this client is connected to, once `connect()` has
85
+ * resolved. `undefined` before the first exchange, unless the constructor
86
+ * was given one.
87
+ */
88
+ get environment(): Environment | undefined;
89
+ /**
90
+ * Mint a token via `getToken` and redeem it at the DP for the session
91
+ * cookie. Call once before issuing task requests.
64
92
  */
65
93
  connect(): Promise<void>;
66
94
  private exchange;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,YAAY,EACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,qBAAa,sBAAsB;IAIrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;gBAEjB,IAAI,EAAE,6BAA6B;IAuBhE,6DAA6D;IAC7D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,6DAA6D;IAC7D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,4EAA4E;IAC5E,IAAI,aAAa,IAAI,mBAAmB,CAEvC;IAED,oEAAoE;IACpE,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,QAAQ;IAiBtB,0EAA0E;YAC5D,UAAU;CAQzB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,YAAY,EACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASzC,MAAM,WAAW,6BAA6B;IAC5C;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,kDAAkD;IAClD,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAED,qBAAa,sBAAsB;IASrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAgB;IAC9C;;;OAGG;IACH,OAAO,CAAC,mBAAmB,CAAC,CAAc;gBAEb,IAAI,EAAE,6BAA6B;IAyBhE,6DAA6D;IAC7D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,6DAA6D;IAC7D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,4EAA4E;IAC5E,IAAI,aAAa,IAAI,mBAAmB,CAEvC;IAED,oEAAoE;IACpE,IAAI,MAAM,IAAI,YAAY,CAEzB;IAED;;;;OAIG;IACH,IAAI,WAAW,IAAI,WAAW,GAAG,SAAS,CAEzC;IAED;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,QAAQ;IAmCtB,0EAA0E;YAC5D,UAAU;CAQzB"}
@@ -14,7 +14,10 @@
14
14
  * is needed, resolves its `runtime_id` server-side. The SPA fetches
15
15
  * the token through the `getToken` callback.
16
16
  * 2. `connect()` redeems the token at the DP `POST /v1/oauth/exchange`
17
- * for the HttpOnly `intro_dp_session` cookie.
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.
18
21
  * 3. Every subsequent call rides that cookie (`credentials: "include"`)
19
22
  * — `client.tasks.start({ runtime_id })`, `.get(...)`, run streaming,
20
23
  * etc.
@@ -31,6 +34,11 @@ export class IntrospectionApiClient {
31
34
  opts;
32
35
  fetchImpl;
33
36
  cookieClients;
37
+ /**
38
+ * Lane established by the most recent exchange. Per instance, never module
39
+ * scope — two clients on one page holding different lanes is the point.
40
+ */
41
+ resolvedEnvironment;
34
42
  constructor(opts) {
35
43
  this.opts = opts;
36
44
  // Native browser `fetch` throws "Illegal invocation" when called as a
@@ -41,11 +49,13 @@ export class IntrospectionApiClient {
41
49
  if (!opts.dpUrl) {
42
50
  throw new Error("IntrospectionApiClient requires a dpUrl");
43
51
  }
52
+ this.resolvedEnvironment = opts.environment;
44
53
  const http = new BrowserHttpClient({
45
54
  apiUrl: opts.dpUrl,
46
55
  additionalHeaders: opts.additionalHeaders,
47
56
  fetch: this.fetchImpl,
48
57
  onUnauthorized: () => this.reexchange(),
58
+ environment: () => this.resolvedEnvironment,
49
59
  });
50
60
  this.cookieClients = {
51
61
  tasks: new TasksClient(http),
@@ -71,8 +81,16 @@ export class IntrospectionApiClient {
71
81
  return this.cookieClients.shares;
72
82
  }
73
83
  /**
74
- * Mint a token via `getToken` and redeem it at the DP for the
75
- * `intro_dp_session` cookie. Call once before issuing task requests.
84
+ * Environment lane this client is connected to, once `connect()` has
85
+ * resolved. `undefined` before the first exchange, unless the constructor
86
+ * was given one.
87
+ */
88
+ get environment() {
89
+ return this.resolvedEnvironment;
90
+ }
91
+ /**
92
+ * Mint a token via `getToken` and redeem it at the DP for the session
93
+ * cookie. Call once before issuing task requests.
76
94
  */
77
95
  async connect() {
78
96
  await this.exchange();
@@ -90,6 +108,19 @@ export class IntrospectionApiClient {
90
108
  });
91
109
  if (!res.ok)
92
110
  throw await toApiError(res);
111
+ // The DP echoes the lane it resolved from the token's claim, so the client
112
+ // learns which cookie it just established without being told. Re-read on
113
+ // every exchange, including the 401 recovery path: if the broker has since
114
+ // switched lanes, a stale value would name a cookie we no longer hold.
115
+ const body = (await res.json().catch(() => ({})));
116
+ if (body.environment) {
117
+ if (this.opts.environment && this.opts.environment !== body.environment) {
118
+ throw new Error(`IntrospectionApiClient is configured for environment ` +
119
+ `"${this.opts.environment}" but getToken() returned a token for ` +
120
+ `"${body.environment}".`);
121
+ }
122
+ this.resolvedEnvironment = body.environment;
123
+ }
93
124
  }
94
125
  /** 401 recovery: re-exchange, reporting success so the call can retry. */
95
126
  async reexchange() {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
@@ -4,14 +4,29 @@
4
4
  * The request/response machinery — URL joining, query building, body
5
5
  * serialization, error mapping, AG-UI stream parsing — lives in the shared
6
6
  * `@introspection-sdk/http` {@link BaseHttpClient}. This module pins the
7
- * browser auth strategy on top of it: the DP is reached with the HttpOnly
8
- * `intro_dp_session` cookie minted by `POST /v1/oauth/exchange`, so every
9
- * request opts into `credentials: "include"` and sends NO bearer token —
10
- * no Introspection credential ever lives in JavaScript. A 401 (expired
11
- * session cookie) triggers a single `onUnauthorized` refresh + retry.
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}.
12
18
  */
13
19
  import { BaseHttpClient, stripTrailingSlash, toApiError } from "@introspection-sdk/http";
20
+ import type { Environment } from "@introspection-sdk/types";
14
21
  export { stripTrailingSlash, toApiError };
22
+ /**
23
+ * Names which environment lane's session cookie the DP should resolve.
24
+ *
25
+ * A selector, not a credential: the cookie it names is still validated
26
+ * server-side, so naming a lane this browser holds no cookie for resolves to
27
+ * nothing rather than granting anything.
28
+ */
29
+ export declare const ENVIRONMENT_HEADER = "x-introspection-environment";
15
30
  export interface BrowserHttpConfig {
16
31
  /** DP REST base URL every request is prefixed with. */
17
32
  apiUrl: string;
@@ -25,6 +40,14 @@ export interface BrowserHttpConfig {
25
40
  * `false` to surface the original error.
26
41
  */
27
42
  onUnauthorized?: () => Promise<boolean>;
43
+ /**
44
+ * Reads the lane established by the most recent exchange.
45
+ *
46
+ * Called per request rather than captured at construction, because the lane
47
+ * is not known until `connect()` resolves — and it can change under a
48
+ * re-exchange.
49
+ */
50
+ environment?: () => Environment | undefined;
28
51
  }
29
52
  /**
30
53
  * Cookie-authenticated HTTP wrapper bound to one DP endpoint. Has no
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/api/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,UAAU,EACX,MAAM,yBAAyB,CAAC;AAKjC,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC;AAE1C,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACzC;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,cAAc;gBACvC,GAAG,EAAE,iBAAiB;CAanC"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/api/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,UAAU,EACX,MAAM,yBAAyB,CAAC;AAEjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAK5D,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC;AAE1C;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,gCAAgC,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,WAAW,GAAG,SAAS,CAAC;CAC7C;AAED;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,cAAc;gBACvC,GAAG,EAAE,iBAAiB;CAkBnC"}
package/dist/api/http.js CHANGED
@@ -4,16 +4,30 @@
4
4
  * The request/response machinery — URL joining, query building, body
5
5
  * serialization, error mapping, AG-UI stream parsing — lives in the shared
6
6
  * `@introspection-sdk/http` {@link BaseHttpClient}. This module pins the
7
- * browser auth strategy on top of it: the DP is reached with the HttpOnly
8
- * `intro_dp_session` cookie minted by `POST /v1/oauth/exchange`, so every
9
- * request opts into `credentials: "include"` and sends NO bearer token —
10
- * no Introspection credential ever lives in JavaScript. A 401 (expired
11
- * session cookie) triggers a single `onUnauthorized` refresh + retry.
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}.
12
18
  */
13
19
  import { BaseHttpClient, stripTrailingSlash, toApiError, } from "@introspection-sdk/http";
14
20
  import { resolveBrowserFetch } from "./fetch.js";
15
21
  // Re-exported for `client.ts`, which redeems the DP session on `connect()`.
16
22
  export { stripTrailingSlash, toApiError };
23
+ /**
24
+ * Names which environment lane's session cookie the DP should resolve.
25
+ *
26
+ * A selector, not a credential: the cookie it names is still validated
27
+ * server-side, so naming a lane this browser holds no cookie for resolves to
28
+ * nothing rather than granting anything.
29
+ */
30
+ export const ENVIRONMENT_HEADER = "x-introspection-environment";
17
31
  /**
18
32
  * Cookie-authenticated HTTP wrapper bound to one DP endpoint. Has no
19
33
  * opinion about which resource it serves — the caller picks the path.
@@ -26,7 +40,12 @@ export class BrowserHttpClient extends BaseHttpClient {
26
40
  fetch: resolveBrowserFetch(cfg.fetch),
27
41
  additionalHeaders: cfg.additionalHeaders,
28
42
  transport: {
29
- authHeaders: () => ({}),
43
+ // Still no bearer token — the HttpOnly cookie is the credential. The
44
+ // only per-request contribution is which lane's cookie to resolve.
45
+ authHeaders: () => {
46
+ const environment = cfg.environment?.();
47
+ return environment ? { [ENVIRONMENT_HEADER]: environment } : {};
48
+ },
30
49
  credentials: "include",
31
50
  onUnauthorized: cfg.onUnauthorized,
32
51
  },
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/api/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,UAAU,GACX,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,4EAA4E;AAC5E,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC;AAiB1C;;;GAGG;AACH,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IACnD,YAAY,GAAsB;QAChC,KAAK,CAAC;YACJ,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,uEAAuE;YACvE,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC;YACrC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;YACxC,SAAS,EAAE;gBACT,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;gBACvB,WAAW,EAAE,SAAS;gBACtB,cAAc,EAAE,GAAG,CAAC,cAAc;aACnC;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/api/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,UAAU,GACX,MAAM,yBAAyB,CAAC;AAIjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,4EAA4E;AAC5E,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,6BAA6B,CAAC;AAyBhE;;;GAGG;AACH,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IACnD,YAAY,GAAsB;QAChC,KAAK,CAAC;YACJ,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,uEAAuE;YACvE,KAAK,EAAE,mBAAmB,CAAC,GAAG,CAAC,KAAK,CAAC;YACrC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;YACxC,SAAS,EAAE;gBACT,qEAAqE;gBACrE,mEAAmE;gBACnE,WAAW,EAAE,GAA2B,EAAE;oBACxC,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC;oBACxC,OAAO,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClE,CAAC;gBACD,WAAW,EAAE,SAAS;gBACtB,cAAc,EAAE,GAAG,CAAC,cAAc;aACnC;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -26,7 +26,7 @@
26
26
  * getToken: () => token,
27
27
  * });
28
28
  *
29
- * await client.connect(); // -> intro_dp_session cookie
29
+ * await client.connect(); // -> intro_dp_<environment> session cookie
30
30
  * const run = await client.tasks.start({
31
31
  * prompt: "Summarize my latest order",
32
32
  * runtime_id: runtimeId,
@@ -37,9 +37,9 @@
37
37
  export { IntrospectionApiClient, type IntrospectionApiClientOptions, } from "./client.js";
38
38
  export { TasksClient, TaskRunsClient, RunHandle, type CreateTaskParams, type StartTaskParams, } from "./tasks.js";
39
39
  export { ConversationItemsClient, ConversationsClient, FileVersionsClient, FilesClient, SharesClient, type FileUploadBody, } from "@introspection-sdk/http";
40
- export { BrowserHttpClient, type BrowserHttpConfig } from "./http.js";
40
+ export { BrowserHttpClient, ENVIRONMENT_HEADER, type BrowserHttpConfig, } from "./http.js";
41
41
  export { Paginator, cursorPaginate, type PageSource, } from "@introspection-sdk/http";
42
42
  export { EventType } from "@introspection-sdk/types";
43
- export type { AGUIEvent, BaseEvent, Interrupt, Message, ResumeEntry, RunAgentInput, Task, TaskRun, TaskStatus, TaskRunKind, TaskCreateResponse, TaskListParams, TaskUpdateParams, TaskRunCreateParams, TaskRunResumeParams, TaskCancelResponse, TaskCancelOptions, RunIdentityInput, Paginated, File, FileType, FileListParams, FileUpdateParams, FileCreateTextParams, ListParams, ConversationSummary, ConversationSortField, ConversationItem, ConversationItemList, ConversationListParams, ConversationItemListParams, ConversationItemInclude, ConversationResponse, ResourceShare, ShareResourceType, ShareCreateParams, ShareListParams, } from "@introspection-sdk/types";
43
+ export type { AGUIEvent, BaseEvent, Environment, Interrupt, Message, ResumeEntry, RunAgentInput, Task, TaskRun, TaskStatus, TaskRunKind, TaskCreateResponse, TaskListParams, TaskUpdateParams, TaskRunCreateParams, TaskRunResumeParams, TaskCancelResponse, TaskCancelOptions, RunIdentityInput, Paginated, File, FileType, FileListParams, FileUpdateParams, FileCreateTextParams, ListParams, ConversationSummary, ConversationSortField, ConversationItem, ConversationItemList, ConversationListParams, ConversationItemListParams, ConversationItemInclude, ConversationResponse, ResourceShare, ShareResourceType, ShareCreateParams, ShareListParams, } from "@introspection-sdk/types";
44
44
  export { IntrospectionAPIError, AuthenticationError, InsufficientScopeError, NotFoundError, NetworkError, RunnerExpiredError, } from "@introspection-sdk/types";
45
45
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EACL,SAAS,EACT,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGrD,YAAY,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,OAAO,EACP,WAAW,EACX,aAAa,EACb,IAAI,EACJ,OAAO,EACP,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,EACvB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,kBAAkB,GACnB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,cAAc,GACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,SAAS,EACT,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAGrD,YAAY,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,OAAO,EACP,WAAW,EACX,aAAa,EACb,IAAI,EACJ,OAAO,EACP,UAAU,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,EACvB,oBAAoB,EACpB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,kBAAkB,GACnB,MAAM,0BAA0B,CAAC"}
package/dist/api/index.js CHANGED
@@ -26,7 +26,7 @@
26
26
  * getToken: () => token,
27
27
  * });
28
28
  *
29
- * await client.connect(); // -> intro_dp_session cookie
29
+ * await client.connect(); // -> intro_dp_<environment> session cookie
30
30
  * const run = await client.tasks.start({
31
31
  * prompt: "Summarize my latest order",
32
32
  * runtime_id: runtimeId,
@@ -37,7 +37,7 @@
37
37
  export { IntrospectionApiClient, } from "./client.js";
38
38
  export { TasksClient, TaskRunsClient, RunHandle, } from "./tasks.js";
39
39
  export { ConversationItemsClient, ConversationsClient, FileVersionsClient, FilesClient, SharesClient, } from "@introspection-sdk/http";
40
- export { BrowserHttpClient } from "./http.js";
40
+ export { BrowserHttpClient, ENVIRONMENT_HEADER, } from "./http.js";
41
41
  export { Paginator, cursorPaginate, } from "@introspection-sdk/http";
42
42
  export { EventType } from "@introspection-sdk/types";
43
43
  export { IntrospectionAPIError, AuthenticationError, InsufficientScopeError, NotFoundError, NetworkError, RunnerExpiredError, } from "@introspection-sdk/types";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EACL,sBAAsB,GAEvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,GAGV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,GAEb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAA0B,MAAM,WAAW,CAAC;AACtE,OAAO,EACL,SAAS,EACT,cAAc,GAEf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA0CrD,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,kBAAkB,GACnB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EACL,sBAAsB,GAEvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,WAAW,EACX,cAAc,EACd,SAAS,GAGV,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,YAAY,GAEb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,iBAAiB,EACjB,kBAAkB,GAEnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,SAAS,EACT,cAAc,GAEf,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AA2CrD,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,YAAY,EACZ,kBAAkB,GACnB,MAAM,0BAA0B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@introspection-sdk/introspection-browser",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Introspection observability SDK for the browser",
6
6
  "engines": {
@@ -31,8 +31,8 @@
31
31
  "@opentelemetry/api-logs": "^0.221.0",
32
32
  "@opentelemetry/sdk-logs": "^0.221.0",
33
33
  "@opentelemetry/exporter-logs-otlp-http": "^0.221.0",
34
- "@introspection-sdk/types": "0.14.0",
35
- "@introspection-sdk/http": "0.14.0"
34
+ "@introspection-sdk/types": "0.16.0",
35
+ "@introspection-sdk/http": "0.16.0"
36
36
  },
37
37
  "scripts": {
38
38
  "lint": "eslint . --max-warnings 0",
package/src/api/client.ts CHANGED
@@ -14,7 +14,10 @@
14
14
  * is needed, resolves its `runtime_id` server-side. The SPA fetches
15
15
  * the token through the `getToken` callback.
16
16
  * 2. `connect()` redeems the token at the DP `POST /v1/oauth/exchange`
17
- * for the HttpOnly `intro_dp_session` cookie.
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.
18
21
  * 3. Every subsequent call rides that cookie (`credentials: "include"`)
19
22
  * — `client.tasks.start({ runtime_id })`, `.get(...)`, run streaming,
20
23
  * etc.
@@ -31,6 +34,7 @@ import {
31
34
  FilesClient,
32
35
  SharesClient,
33
36
  } from "@introspection-sdk/http";
37
+ import type { Environment } from "@introspection-sdk/types";
34
38
  import { TasksClient } from "./tasks.js";
35
39
 
36
40
  type CookieClients = {
@@ -59,11 +63,29 @@ export interface IntrospectionApiClientOptions {
59
63
  fetch?: typeof fetch;
60
64
  /** Extra headers merged into every DP request. */
61
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;
62
79
  }
63
80
 
64
81
  export class IntrospectionApiClient {
65
82
  private readonly fetchImpl: typeof fetch;
66
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;
67
89
 
68
90
  constructor(private readonly opts: IntrospectionApiClientOptions) {
69
91
  // Native browser `fetch` throws "Illegal invocation" when called as a
@@ -74,11 +96,13 @@ export class IntrospectionApiClient {
74
96
  if (!opts.dpUrl) {
75
97
  throw new Error("IntrospectionApiClient requires a dpUrl");
76
98
  }
99
+ this.resolvedEnvironment = opts.environment;
77
100
  const http = new BrowserHttpClient({
78
101
  apiUrl: opts.dpUrl,
79
102
  additionalHeaders: opts.additionalHeaders,
80
103
  fetch: this.fetchImpl,
81
104
  onUnauthorized: () => this.reexchange(),
105
+ environment: () => this.resolvedEnvironment,
82
106
  });
83
107
  this.cookieClients = {
84
108
  tasks: new TasksClient(http),
@@ -109,8 +133,17 @@ export class IntrospectionApiClient {
109
133
  }
110
134
 
111
135
  /**
112
- * Mint a token via `getToken` and redeem it at the DP for the
113
- * `intro_dp_session` cookie. Call once before issuing task requests.
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.
114
147
  */
115
148
  async connect(): Promise<void> {
116
149
  await this.exchange();
@@ -131,6 +164,24 @@ export class IntrospectionApiClient {
131
164
  },
132
165
  );
133
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
+ }
134
185
  }
135
186
 
136
187
  /** 401 recovery: re-exchange, reporting success so the call can retry. */
package/src/api/http.ts CHANGED
@@ -4,11 +4,17 @@
4
4
  * The request/response machinery — URL joining, query building, body
5
5
  * serialization, error mapping, AG-UI stream parsing — lives in the shared
6
6
  * `@introspection-sdk/http` {@link BaseHttpClient}. This module pins the
7
- * browser auth strategy on top of it: the DP is reached with the HttpOnly
8
- * `intro_dp_session` cookie minted by `POST /v1/oauth/exchange`, so every
9
- * request opts into `credentials: "include"` and sends NO bearer token —
10
- * no Introspection credential ever lives in JavaScript. A 401 (expired
11
- * session cookie) triggers a single `onUnauthorized` refresh + retry.
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}.
12
18
  */
13
19
 
14
20
  import {
@@ -17,11 +23,22 @@ import {
17
23
  toApiError,
18
24
  } from "@introspection-sdk/http";
19
25
 
26
+ import type { Environment } from "@introspection-sdk/types";
27
+
20
28
  import { resolveBrowserFetch } from "./fetch.js";
21
29
 
22
30
  // Re-exported for `client.ts`, which redeems the DP session on `connect()`.
23
31
  export { stripTrailingSlash, toApiError };
24
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
+
25
42
  export interface BrowserHttpConfig {
26
43
  /** DP REST base URL every request is prefixed with. */
27
44
  apiUrl: string;
@@ -35,6 +52,14 @@ export interface BrowserHttpConfig {
35
52
  * `false` to surface the original error.
36
53
  */
37
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;
38
63
  }
39
64
 
40
65
  /**
@@ -49,7 +74,12 @@ export class BrowserHttpClient extends BaseHttpClient {
49
74
  fetch: resolveBrowserFetch(cfg.fetch),
50
75
  additionalHeaders: cfg.additionalHeaders,
51
76
  transport: {
52
- authHeaders: () => ({}),
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
+ },
53
83
  credentials: "include",
54
84
  onUnauthorized: cfg.onUnauthorized,
55
85
  },
package/src/api/index.ts CHANGED
@@ -26,7 +26,7 @@
26
26
  * getToken: () => token,
27
27
  * });
28
28
  *
29
- * await client.connect(); // -> intro_dp_session cookie
29
+ * await client.connect(); // -> intro_dp_<environment> session cookie
30
30
  * const run = await client.tasks.start({
31
31
  * prompt: "Summarize my latest order",
32
32
  * runtime_id: runtimeId,
@@ -54,7 +54,11 @@ export {
54
54
  SharesClient,
55
55
  type FileUploadBody,
56
56
  } from "@introspection-sdk/http";
57
- export { BrowserHttpClient, type BrowserHttpConfig } from "./http.js";
57
+ export {
58
+ BrowserHttpClient,
59
+ ENVIRONMENT_HEADER,
60
+ type BrowserHttpConfig,
61
+ } from "./http.js";
58
62
  export {
59
63
  Paginator,
60
64
  cursorPaginate,
@@ -66,6 +70,7 @@ export { EventType } from "@introspection-sdk/types";
66
70
  export type {
67
71
  AGUIEvent,
68
72
  BaseEvent,
73
+ Environment,
69
74
  Interrupt,
70
75
  Message,
71
76
  ResumeEntry,