@cerebro-labs/agentnava-sdk 0.53.2 → 0.75.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.
package/http.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // The transport. Everything else in this SDK is objects and method names.
2
- import { AgentNavaError, networkError } from "./errors.js";
2
+ import { AgentNavaError, networkError, SecretHostNotAllowed } from "./errors.js";
3
3
  import { SDK_VERSION } from "./version.js";
4
4
  /** Tells the server which SDK is calling, on every request.
5
5
  *
@@ -50,23 +50,26 @@ function readEnv(name) {
50
50
  }
51
51
  }
52
52
  export class Http {
53
- #apiKey;
53
+ #authorization;
54
54
  #baseURL;
55
55
  #timeout;
56
56
  #doFetch;
57
- constructor(options = {}) {
58
- const key = options.apiKey ?? readEnv('AGENTNAVA_API_KEY');
57
+ /** `authorization`: the whole header, for a caller that signs in with
58
+ * something other than a workspace key, a partner app's own client id and
59
+ * secret (partner-app.ts). Otherwise the key, as `Bearer`. */
60
+ constructor(options = {}, authorization) {
61
+ const key = authorization ? undefined : (options.apiKey ?? readEnv('AGENTNAVA_API_KEY'));
59
62
  // Thrown at construction, not at the first call. A missing key is a
60
63
  // configuration mistake, and finding it three layers into an async call
61
64
  // stack makes it look like the call was at fault.
62
- if (!key) {
65
+ if (!authorization && !key) {
63
66
  throw new AgentNavaError({
64
67
  message: 'No API key. Pass `AgentNava.getWorkspace({ apiKey })` or set AGENTNAVA_API_KEY in the environment.',
65
68
  code: 'no_api_key',
66
69
  status: 401,
67
70
  });
68
71
  }
69
- this.#apiKey = key;
72
+ this.#authorization = authorization ?? `Bearer ${key}`;
70
73
  this.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
71
74
  this.#timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
72
75
  this.#doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);
@@ -82,7 +85,7 @@ export class Http {
82
85
  /** Make the request and hand back the raw Response, errors already thrown. */
83
86
  async send(path, options = {}) {
84
87
  const headers = {
85
- Authorization: `Bearer ${this.#apiKey}`,
88
+ Authorization: this.#authorization,
86
89
  [CLIENT_HEADER]: CLIENT_ID,
87
90
  };
88
91
  if (options.idempotencyKey)
@@ -251,6 +254,7 @@ async function toError(res, path, idempotencyKey) {
251
254
  let message = `${res.status} from ${path}`;
252
255
  let code = `http_${res.status}`;
253
256
  let retryable;
257
+ let hostRefusal;
254
258
  try {
255
259
  const text = await res.text();
256
260
  if (text) {
@@ -259,6 +263,13 @@ async function toError(res, path, idempotencyKey) {
259
263
  message = parsed.message ?? parsed.error ?? text.slice(0, 300);
260
264
  if (parsed.code)
261
265
  code = parsed.code;
266
+ if (parsed.code === 'secret_host_not_allowed') {
267
+ hostRefusal = {
268
+ ...(typeof parsed.secret === 'string' && { secret: parsed.secret }),
269
+ ...(typeof parsed.host === 'string' && { host: parsed.host }),
270
+ ...(Array.isArray(parsed.allowedHosts) && { allowedHosts: parsed.allowedHosts.filter((x) => typeof x === 'string') }),
271
+ };
272
+ }
262
273
  if (parsed.retryable === false)
263
274
  retryable = false;
264
275
  }
@@ -273,13 +284,14 @@ async function toError(res, path, idempotencyKey) {
273
284
  const retryAfterHeader = res.headers.get('retry-after');
274
285
  const retryAfter = retryAfterHeader ? Number(retryAfterHeader) : undefined;
275
286
  const requestId = res.headers.get('x-request-id') ?? undefined;
276
- const failure = new AgentNavaError({
287
+ const init = {
277
288
  message: idempotencyKey ? `${message} ${turnRecoveryMessage(path, idempotencyKey)}` : message,
278
289
  code,
279
290
  status: res.status,
280
291
  ...(retryAfter !== undefined && Number.isFinite(retryAfter) && { retryAfter }),
281
292
  ...(requestId !== undefined && { requestId }),
282
- });
293
+ };
294
+ const failure = hostRefusal ? new SecretHostNotAllowed({ ...init, ...hostRefusal }) : new AgentNavaError(init);
283
295
  // An uncertain handoff can be HTTP 503 while already owning accepted work.
284
296
  // Respect the server's refusal to retry instead of creating a second turn.
285
297
  if (retryable === false || (idempotencyKey && res.status >= 500))
package/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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","sourcesContent":["// The transport. Everything else in this SDK is objects and method names.\n\nimport { AgentNavaError, networkError } from './errors.ts';\nimport { SDK_VERSION } from './version.ts';\n\nexport interface ClientOptions {\n\t/** Defaults to `AGENTNAVA_API_KEY` from the environment. */\n\tapiKey?: string;\n\t/** Defaults to `https://api.agentnava.com`. */\n\tbaseURL?: string;\n\t/** Per-request timeout in milliseconds. Default 60_000. */\n\ttimeout?: number;\n\t/** Swap the fetch implementation, for tests or a proxy. */\n\tfetch?: typeof globalThis.fetch;\n}\n\n/** Tells the server which SDK is calling, on every request.\n *\n * WHY A CUSTOM HEADER AND NOT `User-Agent`. `User-Agent` is on the fetch\n * spec's forbidden-header list, so a browser DROPS it SILENTLY: the request\n * still goes, the header just is not there, and the server sees a client of\n * unknown age with nothing to indicate anything went wrong. A header we name\n * ourselves is sent by every runtime the same way. `X-AgentNava-User-Id`\n * already exists on this API, so the shape is not new.\n *\n * WHAT IT IS FOR. Not gating: the server does not refuse an old client on\n * sight. It is so the question \"who would break if we removed this field\"\n * has an answer that is a query rather than a guess. Once the package is on\n * a public registry, a customer can pin 0.25.0 and still be calling in three\n * years, and we have no other way to know they exist.\n *\n * The runtime note is best-effort and deliberately coarse. It separates Node\n * from Bun from a browser, which is the distinction that explains most\n * support questions, and carries nothing identifying. */\nconst CLIENT_HEADER = 'X-AgentNava-Client';\n\nfunction runtimeTag(): string {\n\tconst g = globalThis as {\n\t\tBun?: { version?: string };\n\t\tprocess?: { versions?: { node?: string } };\n\t\tdocument?: unknown;\n\t};\n\tif (g.Bun?.version) return `bun/${g.Bun.version}`;\n\tif (g.process?.versions?.node) return `node/${g.process.versions.node}`;\n\tif (g.document !== undefined) return 'browser';\n\treturn 'unknown';\n}\n\n/** `agentnava-sdk/0.25.0 (node/22.20.0)`. Computed once: it cannot change\n * within a process, and rebuilding it per request buys nothing. */\nconst CLIENT_ID = `agentnava-sdk/${SDK_VERSION} (${runtimeTag()})`;\n\nconst DEFAULT_BASE_URL = 'https://api.agentnava.com';\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Read a variable without assuming which runtime we are on.\n *\n * This SDK runs in Node, Bun, Deno, Workers and a browser bundle. `process` is\n * absent in several of those and touching it throws rather than returning\n * undefined, so the access is guarded rather than defaulted. */\nfunction readEnv(name: string): string | undefined {\n\ttry {\n\t\tconst proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n\t\treturn proc?.env?.[name];\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport interface RequestOptions {\n\tmethod?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';\n\t/** Internal turn submission identity, minted once by Conversation.ask. */\n\tidempotencyKey?: string;\n\t/** Sent as a JSON body. Mutually exclusive with `bytes`. */\n\tbody?: unknown;\n\t/** Sent as the raw body, for file writes. Mutually exclusive with `body`. */\n\tbytes?: ArrayBuffer | Uint8Array;\n\tcontentType?: string;\n\tquery?: Record<string, string | number | boolean | undefined>;\n\t/* THERE IS DELIBERATELY NO `raw` FLAG.\n\t *\n\t * One was declared here, described as \"return the Response untouched\", and\n\t * read by nothing. `request` parses JSON unconditionally, so every caller\n\t * that asked for `raw: true` got a parsed body and then called `.text()` or\n\t * `.arrayBuffer()` on it. `kb.files.text()` and `kb.files.get()` were broken\n\t * for every developer from the day they shipped, and the flag is why: it made\n\t * the call site look correct.\n\t *\n\t * Use `send()` for a Response and `request<T>()` for parsed JSON. Two methods\n\t * that differ by return type cannot be confused the way one method with a\n\t * boolean can. */\n\tsignal?: AbortSignal;\n}\n\nexport class Http {\n\treadonly #apiKey: string;\n\treadonly #baseURL: string;\n\treadonly #timeout: number;\n\treadonly #doFetch: typeof globalThis.fetch;\n\n\tconstructor(options: ClientOptions = {}) {\n\t\tconst key = options.apiKey ?? readEnv('AGENTNAVA_API_KEY');\n\t\t// Thrown at construction, not at the first call. A missing key is a\n\t\t// configuration mistake, and finding it three layers into an async call\n\t\t// stack makes it look like the call was at fault.\n\t\tif (!key) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\t'No API key. Pass `AgentNava.getWorkspace({ apiKey })` or set AGENTNAVA_API_KEY in the environment.',\n\t\t\t\tcode: 'no_api_key',\n\t\t\t\tstatus: 401,\n\t\t\t});\n\t\t}\n\t\tthis.#apiKey = key;\n\t\tthis.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? DEFAULT_BASE_URL).replace(\n\t\t\t/\\/+$/,\n\t\t\t'',\n\t\t);\n\t\tthis.#timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.#doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n\t}\n\n\tprivate url(path: string, query?: RequestOptions['query']): string {\n\t\tconst url = new URL(this.#baseURL + path);\n\t\tfor (const [k, v] of Object.entries(query ?? {})) {\n\t\t\tif (v !== undefined) url.searchParams.set(k, String(v));\n\t\t}\n\t\treturn url.toString();\n\t}\n\n\t/** Make the request and hand back the raw Response, errors already thrown. */\n\tasync send(path: string, options: RequestOptions = {}): Promise<Response> {\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: `Bearer ${this.#apiKey}`,\n\t\t\t[CLIENT_HEADER]: CLIENT_ID,\n\t\t};\n\t\tif (options.idempotencyKey) headers['Idempotency-Key'] = options.idempotencyKey;\n\t\tlet body: BodyInit | undefined;\n\t\tif (options.bytes !== undefined) {\n\t\t\tbody = options.bytes as BodyInit;\n\t\t\theaders['Content-Type'] = options.contentType ?? 'application/octet-stream';\n\t\t} else if (options.body !== undefined) {\n\t\t\tbody = JSON.stringify(options.body);\n\t\t\theaders['Content-Type'] = 'application/json';\n\t\t}\n\n\t\t// The caller's signal and our timeout both have to be able to abort, and\n\t\t// AbortSignal.any is too new to rely on across every runtime this ships to.\n\t\tconst controller = new AbortController();\n\t\tconst timer = setTimeout(() => controller.abort(), this.#timeout);\n\t\tconst onAbort = () => controller.abort(options.signal?.reason);\n\t\tconst releaseSignal = () => options.signal?.removeEventListener('abort', onAbort);\n\t\tif (options.signal?.aborted) onAbort();\n\t\telse options.signal?.addEventListener('abort', onAbort, { once: true });\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\t// Do not invoke even a custom fetch for a request already cancelled.\n\t\t\t// Such a fetch may not itself honor an already-aborted signal.\n\t\t\tif (controller.signal.aborted) throw controller.signal.reason;\n\t\t\tres = await this.#doFetch(this.url(path, options.query), {\n\t\t\t\tmethod: options.method ?? 'GET',\n\t\t\t\theaders,\n\t\t\t\t...(body !== undefined && { body }),\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\treleaseSignal();\n\t\t\tif (options.signal?.aborted) throw err;\n\t\t\tconst failure = networkError(\n\t\t\t\tcontroller.signal.aborted\n\t\t\t\t\t? `Request to ${path} timed out after ${this.#timeout}ms`\n\t\t\t\t\t: `Request to ${path} failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\tif (options.idempotencyKey) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `${failure.message} The turn may have been accepted. ${turnRecoveryMessage(path, options.idempotencyKey)}`,\n\t\t\t\t\tcode: 'network_error', status: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow failure;\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\n\t\t// Headers end the connection timeout, not the caller's right to stop\n\t\t// reading. Keep forwarding cancellation for the lifetime of the body.\n\t\tres = responseUntilConsumed(res, releaseSignal, (reason) => controller.abort(reason));\n\t\tif (!res.ok) throw await toError(res, path, options.idempotencyKey);\n\t\treturn res;\n\t}\n\n\tasync request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n\t\tconst res = await this.send(path, options);\n\t\t// 204, and any other empty body, is a successful nothing rather than a\n\t\t// JSON parse error.\n\t\tif (res.status === 204) return undefined as T;\n\t\tconst text = await res.text();\n\t\tif (text.length === 0) return undefined as T;\n\t\ttry {\n\t\t\treturn JSON.parse(text) as T;\n\t\t} catch {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `Expected JSON from ${path}, got: ${text.slice(0, 200)}`,\n\t\t\t\tcode: 'bad_response',\n\t\t\t\tstatus: res.status,\n\t\t\t});\n\t\t}\n\t}\n\n\tget<T>(path: string, query?: RequestOptions['query']): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'GET', ...(query && { query }) });\n\t}\n\tpost<T>(path: string, body?: unknown, query?: RequestOptions['query']): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'POST', body, ...(query && { query }) });\n\t}\n\tput<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'PUT', body });\n\t}\n\tpatch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'PATCH', body });\n\t}\n\tdelete<T>(path: string, query?: RequestOptions['query']): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'DELETE', ...(query && { query }) });\n\t}\n}\n\n/** Dispose the caller's abort listener after EOF, failure or cancellation.\n * Http.send is internal: callers consume status, headers and body, so wrapping\n * the body preserves the values the SDK exposes without buffering a stream. */\nfunction responseUntilConsumed(\n\tresponse: Response,\n\treleaseSignal: () => void,\n\tabortFetch: (reason?: unknown) => void,\n): Response {\n\tif (!response.body) {\n\t\treleaseSignal();\n\t\treturn response;\n\t}\n\tconst reader = response.body.getReader();\n\tlet finished = false;\n\tlet released = false;\n\tconst release = () => {\n\t\tif (released) return;\n\t\treleased = true;\n\t\treleaseSignal();\n\t\t// Cancellation settles pending reads before lock release, even when the\n\t\t// underlying source's cleanup promise has not settled.\n\t\ttry { reader.releaseLock(); } catch { /* cleanup preserves the original result */ }\n\t};\n\treturn new Response(new ReadableStream<Uint8Array>({\n\t\tasync pull(controller) {\n\t\t\ttry {\n\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\tif (finished) return;\n\t\t\t\tif (done) {\n\t\t\t\t\tfinished = true;\n\t\t\t\t\trelease();\n\t\t\t\t\tcontroller.close();\n\t\t\t\t} else controller.enqueue(value);\n\t\t\t} catch (error) {\n\t\t\t\tif (finished) return;\n\t\t\t\tfinished = true;\n\t\t\t\trelease();\n\t\t\t\tcontroller.error(error);\n\t\t\t}\n\t\t},\n\t\tcancel(reason) {\n\t\t\tif (finished) return;\n\t\t\tfinished = true;\n\t\t\t// Bun can leave a real fetch socket open after reader.cancel(). Abort\n\t\t\t// the transport as well, including requests with no caller signal.\n\t\t\tabortFetch(reason);\n\t\t\ttry {\n\t\t\t\tvoid reader.cancel(reason).catch(() => {});\n\t\t\t} catch { /* a custom reader may throw synchronously during cleanup */ }\n\t\t\tfinally { release(); }\n\t\t},\n\t}, { highWaterMark: 0 }), {\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t\theaders: response.headers,\n\t});\n}\n\n/** Turn a failed Response into the one error type this SDK throws. */\nasync function toError(res: Response, path: string, idempotencyKey?: string): Promise<AgentNavaError> {\n\tlet message = `${res.status} from ${path}`;\n\tlet code = `http_${res.status}`;\n\tlet retryable: boolean | undefined;\n\ttry {\n\t\tconst text = await res.text();\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(text) as { message?: string; code?: string; error?: string; retryable?: boolean };\n\t\t\t\tmessage = parsed.message ?? parsed.error ?? text.slice(0, 300);\n\t\t\t\tif (parsed.code) code = parsed.code;\n\t\t\t\tif (parsed.retryable === false) retryable = false;\n\t\t\t} catch {\n\t\t\t\tmessage = text.slice(0, 300);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// A body we cannot read is not worth failing over; the status still is.\n\t}\n\n\tconst retryAfterHeader = res.headers.get('retry-after');\n\tconst retryAfter = retryAfterHeader ? Number(retryAfterHeader) : undefined;\n\tconst requestId = res.headers.get('x-request-id') ?? undefined;\n\n\tconst failure = new AgentNavaError({\n\t\tmessage: idempotencyKey ? `${message} ${turnRecoveryMessage(path, idempotencyKey)}` : message,\n\t\tcode,\n\t\tstatus: res.status,\n\t\t...(retryAfter !== undefined && Number.isFinite(retryAfter) && { retryAfter }),\n\t\t...(requestId !== undefined && { requestId }),\n\t});\n\t// An uncertain handoff can be HTTP 503 while already owning accepted work.\n\t// Respect the server's refusal to retry instead of creating a second turn.\n\tif (retryable === false || (idempotencyKey && res.status >= 500)) Object.defineProperty(failure, 'retryable', { value: false, enumerable: true });\n\treturn failure;\n}\n\n/** Kept in errors because a dropped response may not have returned a turn ID. */\nexport function turnRecoveryMessage(path: string, idempotencyKey: string): string {\n\treturn `Check the conversation before starting another turn. To check status, POST the original body to ${path} with the same authorization, Idempotency-Key: ${idempotencyKey} and X-Turn-Status: true.`;\n}\n"]}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAa3C;;;;;;;;;;;;;;;;;0DAiB0D;AAC1D,MAAM,aAAa,GAAG,oBAAoB,CAAC;AAE3C,SAAS,UAAU;IAClB,MAAM,CAAC,GAAG,UAIT,CAAC;IACF,IAAI,CAAC,CAAC,GAAG,EAAE,OAAO;QAAE,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IAClD,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI;QAAE,OAAO,QAAQ,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACxE,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC/C,OAAO,SAAS,CAAC;AAClB,CAAC;AAED;oEACoE;AACpE,MAAM,SAAS,GAAG,iBAAiB,WAAW,KAAK,UAAU,EAAE,GAAG,CAAC;AAEnE,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;AACrD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC;;;;iEAIiE;AACjE,SAAS,OAAO,CAAC,IAAY;IAC5B,IAAI,CAAC;QACJ,MAAM,IAAI,GAAI,UAAyE,CAAC,OAAO,CAAC;QAChG,OAAO,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AA2BD,MAAM,OAAO,IAAI;IACP,cAAc,CAAS;IACvB,QAAQ,CAAS;IACjB,QAAQ,CAAS;IACjB,QAAQ,CAA0B;IAE3C;;mEAE+D;IAC/D,YAAY,UAAyB,EAAE,EAAE,aAAsB;QAC9D,MAAM,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;QACzF,oEAAoE;QACpE,wEAAwE;QACxE,kDAAkD;QAClD,IAAI,CAAC,aAAa,IAAI,CAAC,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,cAAc,CAAC;gBACxB,OAAO,EACN,oGAAoG;gBACrG,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,GAAG;aACX,CAAC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,cAAc,GAAG,aAAa,IAAI,UAAU,GAAG,EAAE,CAAC;QACvD,IAAI,CAAC,QAAQ,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,oBAAoB,CAAC,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAC7F,MAAM,EACN,EAAE,CACF,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,IAAI,kBAAkB,CAAC;QACtD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpE,CAAC;IAEO,GAAG,CAAC,IAAY,EAAE,KAA+B;QACxD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;YAClD,IAAI,CAAC,KAAK,SAAS;gBAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACvB,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,UAA0B,EAAE;QACpD,MAAM,OAAO,GAA2B;YACvC,aAAa,EAAE,IAAI,CAAC,cAAc;YAClC,CAAC,aAAa,CAAC,EAAE,SAAS;SAC1B,CAAC;QACF,IAAI,OAAO,CAAC,cAAc;YAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;QAChF,IAAI,IAA0B,CAAC;QAC/B,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACjC,IAAI,GAAG,OAAO,CAAC,KAAiB,CAAC;YACjC,OAAO,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,WAAW,IAAI,0BAA0B,CAAC;QAC7E,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YACvC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACpC,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC9C,CAAC;QAED,yEAAyE;QACzE,4EAA4E;QAC5E,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/D,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAClF,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO;YAAE,OAAO,EAAE,CAAC;;YAClC,OAAO,CAAC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAExE,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACJ,qEAAqE;YACrE,+DAA+D;YAC/D,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;gBAAE,MAAM,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC;YAC9D,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE;gBACxD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;gBAC/B,OAAO;gBACP,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;gBACnC,MAAM,EAAE,UAAU,CAAC,MAAM;aACzB,CAAC,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,aAAa,EAAE,CAAC;YAChB,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO;gBAAE,MAAM,GAAG,CAAC;YACvC,MAAM,OAAO,GAAG,YAAY,CAC3B,UAAU,CAAC,MAAM,CAAC,OAAO;gBACxB,CAAC,CAAC,cAAc,IAAI,oBAAoB,IAAI,CAAC,QAAQ,IAAI;gBACzD,CAAC,CAAC,cAAc,IAAI,YAAY,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EACnF,GAAG,CACH,CAAC;YACF,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;gBAC5B,MAAM,IAAI,cAAc,CAAC;oBACxB,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,qCAAqC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,EAAE;oBACnH,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;iBAChC,CAAC,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,CAAC;QACf,CAAC;gBAAS,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QAED,qEAAqE;QACrE,sEAAsE;QACtE,GAAG,GAAG,qBAAqB,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,MAAM,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;QACpE,OAAO,GAAG,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,OAAO,CAAI,IAAY,EAAE,UAA0B,EAAE;QAC1D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,uEAAuE;QACvE,oBAAoB;QACpB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,SAAc,CAAC;QAC9C,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAc,CAAC;QAC7C,IAAI,CAAC;YACJ,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,IAAI,cAAc,CAAC;gBACxB,OAAO,EAAE,sBAAsB,IAAI,UAAU,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;gBACjE,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,GAAG,CAAC,MAAM;aAClB,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,GAAG,CAAI,IAAY,EAAE,KAA+B;QACnD,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,CAAI,IAAY,EAAE,IAAc,EAAE,KAA+B;QACpE,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACjF,CAAC;IACD,GAAG,CAAI,IAAY,EAAE,IAAc;QAClC,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,KAAK,CAAI,IAAY,EAAE,IAAc;QACpC,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,CAAI,IAAY,EAAE,KAA+B;QACtD,OAAO,IAAI,CAAC,OAAO,CAAI,IAAI,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;CACD;AAED;;+EAE+E;AAC/E,SAAS,qBAAqB,CAC7B,QAAkB,EAClB,aAAyB,EACzB,UAAsC;IAEtC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpB,aAAa,EAAE,CAAC;QAChB,OAAO,QAAQ,CAAC;IACjB,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,OAAO,GAAG,GAAG,EAAE;QACpB,IAAI,QAAQ;YAAE,OAAO;QACrB,QAAQ,GAAG,IAAI,CAAC;QAChB,aAAa,EAAE,CAAC;QAChB,wEAAwE;QACxE,uDAAuD;QACvD,IAAI,CAAC;YAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,2CAA2C,CAAC,CAAC;IACpF,CAAC,CAAC;IACF,OAAO,IAAI,QAAQ,CAAC,IAAI,cAAc,CAAa;QAClD,KAAK,CAAC,IAAI,CAAC,UAAU;YACpB,IAAI,CAAC;gBACJ,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;gBAC5C,IAAI,QAAQ;oBAAE,OAAO;gBACrB,IAAI,IAAI,EAAE,CAAC;oBACV,QAAQ,GAAG,IAAI,CAAC;oBAChB,OAAO,EAAE,CAAC;oBACV,UAAU,CAAC,KAAK,EAAE,CAAC;gBACpB,CAAC;;oBAAM,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,OAAO,EAAE,CAAC;gBACV,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACF,CAAC;QACD,MAAM,CAAC,MAAM;YACZ,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,sEAAsE;YACtE,mEAAmE;YACnE,UAAU,CAAC,MAAM,CAAC,CAAC;YACnB,IAAI,CAAC;gBACJ,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC,CAAC,4DAA4D,CAAC,CAAC;oBAChE,CAAC;gBAAC,OAAO,EAAE,CAAC;YAAC,CAAC;QACvB,CAAC;KACD,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC,EAAE;QACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO,EAAE,QAAQ,CAAC,OAAO;KACzB,CAAC,CAAC;AACJ,CAAC;AAED,sEAAsE;AACtE,KAAK,UAAU,OAAO,CAAC,GAAa,EAAE,IAAY,EAAE,cAAuB;IAC1E,IAAI,OAAO,GAAG,GAAG,GAAG,CAAC,MAAM,SAAS,IAAI,EAAE,CAAC;IAC3C,IAAI,IAAI,GAAG,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC;IAChC,IAAI,SAA8B,CAAC;IACnC,IAAI,WAAoF,CAAC;IACzF,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,EAAE,CAAC;YACV,IAAI,CAAC;gBACJ,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAG7B,CAAC;gBACF,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBAC/D,IAAI,MAAM,CAAC,IAAI;oBAAE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;gBACpC,IAAI,MAAM,CAAC,IAAI,KAAK,yBAAyB,EAAE,CAAC;oBAC/C,WAAW,GAAG;wBACb,GAAG,CAAC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;wBACnE,GAAG,CAAC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;wBAC7D,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC;qBAClI,CAAC;gBACH,CAAC;gBACD,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK;oBAAE,SAAS,GAAG,KAAK,CAAC;YACnD,CAAC;YAAC,MAAM,CAAC;gBACR,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC9B,CAAC;QACF,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,wEAAwE;IACzE,CAAC;IAED,MAAM,gBAAgB,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACxD,MAAM,UAAU,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3E,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAC;IAE/D,MAAM,IAAI,GAAG;QACZ,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,mBAAmB,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO;QAC7F,IAAI;QACJ,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,CAAC;QAC9E,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;KAC7C,CAAC;IACF,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,oBAAoB,CAAC,EAAE,GAAG,IAAI,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IAC/G,2EAA2E;IAC3E,2EAA2E;IAC3E,IAAI,SAAS,KAAK,KAAK,IAAI,CAAC,cAAc,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC;QAAE,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAClJ,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,cAAsB;IACvE,OAAO,mGAAmG,IAAI,kDAAkD,cAAc,2BAA2B,CAAC;AAC3M,CAAC","sourcesContent":["// The transport. Everything else in this SDK is objects and method names.\n\nimport { AgentNavaError, networkError, SecretHostNotAllowed } from './errors.ts';\nimport { SDK_VERSION } from './version.ts';\n\nexport interface ClientOptions {\n\t/** Defaults to `AGENTNAVA_API_KEY` from the environment. */\n\tapiKey?: string;\n\t/** Defaults to `https://api.agentnava.com`. */\n\tbaseURL?: string;\n\t/** Per-request timeout in milliseconds. Default 60_000. */\n\ttimeout?: number;\n\t/** Swap the fetch implementation, for tests or a proxy. */\n\tfetch?: typeof globalThis.fetch;\n}\n\n/** Tells the server which SDK is calling, on every request.\n *\n * WHY A CUSTOM HEADER AND NOT `User-Agent`. `User-Agent` is on the fetch\n * spec's forbidden-header list, so a browser DROPS it SILENTLY: the request\n * still goes, the header just is not there, and the server sees a client of\n * unknown age with nothing to indicate anything went wrong. A header we name\n * ourselves is sent by every runtime the same way. `X-AgentNava-User-Id`\n * already exists on this API, so the shape is not new.\n *\n * WHAT IT IS FOR. Not gating: the server does not refuse an old client on\n * sight. It is so the question \"who would break if we removed this field\"\n * has an answer that is a query rather than a guess. Once the package is on\n * a public registry, a customer can pin 0.25.0 and still be calling in three\n * years, and we have no other way to know they exist.\n *\n * The runtime note is best-effort and deliberately coarse. It separates Node\n * from Bun from a browser, which is the distinction that explains most\n * support questions, and carries nothing identifying. */\nconst CLIENT_HEADER = 'X-AgentNava-Client';\n\nfunction runtimeTag(): string {\n\tconst g = globalThis as {\n\t\tBun?: { version?: string };\n\t\tprocess?: { versions?: { node?: string } };\n\t\tdocument?: unknown;\n\t};\n\tif (g.Bun?.version) return `bun/${g.Bun.version}`;\n\tif (g.process?.versions?.node) return `node/${g.process.versions.node}`;\n\tif (g.document !== undefined) return 'browser';\n\treturn 'unknown';\n}\n\n/** `agentnava-sdk/0.25.0 (node/22.20.0)`. Computed once: it cannot change\n * within a process, and rebuilding it per request buys nothing. */\nconst CLIENT_ID = `agentnava-sdk/${SDK_VERSION} (${runtimeTag()})`;\n\nconst DEFAULT_BASE_URL = 'https://api.agentnava.com';\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\n/** Read a variable without assuming which runtime we are on.\n *\n * This SDK runs in Node, Bun, Deno, Workers and a browser bundle. `process` is\n * absent in several of those and touching it throws rather than returning\n * undefined, so the access is guarded rather than defaulted. */\nfunction readEnv(name: string): string | undefined {\n\ttry {\n\t\tconst proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;\n\t\treturn proc?.env?.[name];\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport interface RequestOptions {\n\tmethod?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';\n\t/** Internal turn submission identity, minted once by Conversation.ask. */\n\tidempotencyKey?: string;\n\t/** Sent as a JSON body. Mutually exclusive with `bytes`. */\n\tbody?: unknown;\n\t/** Sent as the raw body, for file writes. Mutually exclusive with `body`. */\n\tbytes?: ArrayBuffer | Uint8Array;\n\tcontentType?: string;\n\tquery?: Record<string, string | number | boolean | undefined>;\n\t/* THERE IS DELIBERATELY NO `raw` FLAG.\n\t *\n\t * One was declared here, described as \"return the Response untouched\", and\n\t * read by nothing. `request` parses JSON unconditionally, so every caller\n\t * that asked for `raw: true` got a parsed body and then called `.text()` or\n\t * `.arrayBuffer()` on it. `kb.files.text()` and `kb.files.get()` were broken\n\t * for every developer from the day they shipped, and the flag is why: it made\n\t * the call site look correct.\n\t *\n\t * Use `send()` for a Response and `request<T>()` for parsed JSON. Two methods\n\t * that differ by return type cannot be confused the way one method with a\n\t * boolean can. */\n\tsignal?: AbortSignal;\n}\n\nexport class Http {\n\treadonly #authorization: string;\n\treadonly #baseURL: string;\n\treadonly #timeout: number;\n\treadonly #doFetch: typeof globalThis.fetch;\n\n\t/** `authorization`: the whole header, for a caller that signs in with\n\t * something other than a workspace key, a partner app's own client id and\n\t * secret (partner-app.ts). Otherwise the key, as `Bearer`. */\n\tconstructor(options: ClientOptions = {}, authorization?: string) {\n\t\tconst key = authorization ? undefined : (options.apiKey ?? readEnv('AGENTNAVA_API_KEY'));\n\t\t// Thrown at construction, not at the first call. A missing key is a\n\t\t// configuration mistake, and finding it three layers into an async call\n\t\t// stack makes it look like the call was at fault.\n\t\tif (!authorization && !key) {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage:\n\t\t\t\t\t'No API key. Pass `AgentNava.getWorkspace({ apiKey })` or set AGENTNAVA_API_KEY in the environment.',\n\t\t\t\tcode: 'no_api_key',\n\t\t\t\tstatus: 401,\n\t\t\t});\n\t\t}\n\t\tthis.#authorization = authorization ?? `Bearer ${key}`;\n\t\tthis.#baseURL = (options.baseURL ?? readEnv('AGENTNAVA_BASE_URL') ?? DEFAULT_BASE_URL).replace(\n\t\t\t/\\/+$/,\n\t\t\t'',\n\t\t);\n\t\tthis.#timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;\n\t\tthis.#doFetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n\t}\n\n\tprivate url(path: string, query?: RequestOptions['query']): string {\n\t\tconst url = new URL(this.#baseURL + path);\n\t\tfor (const [k, v] of Object.entries(query ?? {})) {\n\t\t\tif (v !== undefined) url.searchParams.set(k, String(v));\n\t\t}\n\t\treturn url.toString();\n\t}\n\n\t/** Make the request and hand back the raw Response, errors already thrown. */\n\tasync send(path: string, options: RequestOptions = {}): Promise<Response> {\n\t\tconst headers: Record<string, string> = {\n\t\t\tAuthorization: this.#authorization,\n\t\t\t[CLIENT_HEADER]: CLIENT_ID,\n\t\t};\n\t\tif (options.idempotencyKey) headers['Idempotency-Key'] = options.idempotencyKey;\n\t\tlet body: BodyInit | undefined;\n\t\tif (options.bytes !== undefined) {\n\t\t\tbody = options.bytes as BodyInit;\n\t\t\theaders['Content-Type'] = options.contentType ?? 'application/octet-stream';\n\t\t} else if (options.body !== undefined) {\n\t\t\tbody = JSON.stringify(options.body);\n\t\t\theaders['Content-Type'] = 'application/json';\n\t\t}\n\n\t\t// The caller's signal and our timeout both have to be able to abort, and\n\t\t// AbortSignal.any is too new to rely on across every runtime this ships to.\n\t\tconst controller = new AbortController();\n\t\tconst timer = setTimeout(() => controller.abort(), this.#timeout);\n\t\tconst onAbort = () => controller.abort(options.signal?.reason);\n\t\tconst releaseSignal = () => options.signal?.removeEventListener('abort', onAbort);\n\t\tif (options.signal?.aborted) onAbort();\n\t\telse options.signal?.addEventListener('abort', onAbort, { once: true });\n\n\t\tlet res: Response;\n\t\ttry {\n\t\t\t// Do not invoke even a custom fetch for a request already cancelled.\n\t\t\t// Such a fetch may not itself honor an already-aborted signal.\n\t\t\tif (controller.signal.aborted) throw controller.signal.reason;\n\t\t\tres = await this.#doFetch(this.url(path, options.query), {\n\t\t\t\tmethod: options.method ?? 'GET',\n\t\t\t\theaders,\n\t\t\t\t...(body !== undefined && { body }),\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\treleaseSignal();\n\t\t\tif (options.signal?.aborted) throw err;\n\t\t\tconst failure = networkError(\n\t\t\t\tcontroller.signal.aborted\n\t\t\t\t\t? `Request to ${path} timed out after ${this.#timeout}ms`\n\t\t\t\t\t: `Request to ${path} failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t\t\terr,\n\t\t\t);\n\t\t\tif (options.idempotencyKey) {\n\t\t\t\tthrow new AgentNavaError({\n\t\t\t\t\tmessage: `${failure.message} The turn may have been accepted. ${turnRecoveryMessage(path, options.idempotencyKey)}`,\n\t\t\t\t\tcode: 'network_error', status: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tthrow failure;\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t}\n\n\t\t// Headers end the connection timeout, not the caller's right to stop\n\t\t// reading. Keep forwarding cancellation for the lifetime of the body.\n\t\tres = responseUntilConsumed(res, releaseSignal, (reason) => controller.abort(reason));\n\t\tif (!res.ok) throw await toError(res, path, options.idempotencyKey);\n\t\treturn res;\n\t}\n\n\tasync request<T>(path: string, options: RequestOptions = {}): Promise<T> {\n\t\tconst res = await this.send(path, options);\n\t\t// 204, and any other empty body, is a successful nothing rather than a\n\t\t// JSON parse error.\n\t\tif (res.status === 204) return undefined as T;\n\t\tconst text = await res.text();\n\t\tif (text.length === 0) return undefined as T;\n\t\ttry {\n\t\t\treturn JSON.parse(text) as T;\n\t\t} catch {\n\t\t\tthrow new AgentNavaError({\n\t\t\t\tmessage: `Expected JSON from ${path}, got: ${text.slice(0, 200)}`,\n\t\t\t\tcode: 'bad_response',\n\t\t\t\tstatus: res.status,\n\t\t\t});\n\t\t}\n\t}\n\n\tget<T>(path: string, query?: RequestOptions['query']): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'GET', ...(query && { query }) });\n\t}\n\tpost<T>(path: string, body?: unknown, query?: RequestOptions['query']): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'POST', body, ...(query && { query }) });\n\t}\n\tput<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'PUT', body });\n\t}\n\tpatch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'PATCH', body });\n\t}\n\tdelete<T>(path: string, query?: RequestOptions['query']): Promise<T> {\n\t\treturn this.request<T>(path, { method: 'DELETE', ...(query && { query }) });\n\t}\n}\n\n/** Dispose the caller's abort listener after EOF, failure or cancellation.\n * Http.send is internal: callers consume status, headers and body, so wrapping\n * the body preserves the values the SDK exposes without buffering a stream. */\nfunction responseUntilConsumed(\n\tresponse: Response,\n\treleaseSignal: () => void,\n\tabortFetch: (reason?: unknown) => void,\n): Response {\n\tif (!response.body) {\n\t\treleaseSignal();\n\t\treturn response;\n\t}\n\tconst reader = response.body.getReader();\n\tlet finished = false;\n\tlet released = false;\n\tconst release = () => {\n\t\tif (released) return;\n\t\treleased = true;\n\t\treleaseSignal();\n\t\t// Cancellation settles pending reads before lock release, even when the\n\t\t// underlying source's cleanup promise has not settled.\n\t\ttry { reader.releaseLock(); } catch { /* cleanup preserves the original result */ }\n\t};\n\treturn new Response(new ReadableStream<Uint8Array>({\n\t\tasync pull(controller) {\n\t\t\ttry {\n\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\tif (finished) return;\n\t\t\t\tif (done) {\n\t\t\t\t\tfinished = true;\n\t\t\t\t\trelease();\n\t\t\t\t\tcontroller.close();\n\t\t\t\t} else controller.enqueue(value);\n\t\t\t} catch (error) {\n\t\t\t\tif (finished) return;\n\t\t\t\tfinished = true;\n\t\t\t\trelease();\n\t\t\t\tcontroller.error(error);\n\t\t\t}\n\t\t},\n\t\tcancel(reason) {\n\t\t\tif (finished) return;\n\t\t\tfinished = true;\n\t\t\t// Bun can leave a real fetch socket open after reader.cancel(). Abort\n\t\t\t// the transport as well, including requests with no caller signal.\n\t\t\tabortFetch(reason);\n\t\t\ttry {\n\t\t\t\tvoid reader.cancel(reason).catch(() => {});\n\t\t\t} catch { /* a custom reader may throw synchronously during cleanup */ }\n\t\t\tfinally { release(); }\n\t\t},\n\t}, { highWaterMark: 0 }), {\n\t\tstatus: response.status,\n\t\tstatusText: response.statusText,\n\t\theaders: response.headers,\n\t});\n}\n\n/** Turn a failed Response into the one error type this SDK throws. */\nasync function toError(res: Response, path: string, idempotencyKey?: string): Promise<AgentNavaError> {\n\tlet message = `${res.status} from ${path}`;\n\tlet code = `http_${res.status}`;\n\tlet retryable: boolean | undefined;\n\tlet hostRefusal: { secret?: string; host?: string; allowedHosts?: string[] } | undefined;\n\ttry {\n\t\tconst text = await res.text();\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(text) as {\n\t\t\t\t\tmessage?: string; code?: string; error?: string; retryable?: boolean;\n\t\t\t\t\tsecret?: unknown; host?: unknown; allowedHosts?: unknown;\n\t\t\t\t};\n\t\t\t\tmessage = parsed.message ?? parsed.error ?? text.slice(0, 300);\n\t\t\t\tif (parsed.code) code = parsed.code;\n\t\t\t\tif (parsed.code === 'secret_host_not_allowed') {\n\t\t\t\t\thostRefusal = {\n\t\t\t\t\t\t...(typeof parsed.secret === 'string' && { secret: parsed.secret }),\n\t\t\t\t\t\t...(typeof parsed.host === 'string' && { host: parsed.host }),\n\t\t\t\t\t\t...(Array.isArray(parsed.allowedHosts) && { allowedHosts: parsed.allowedHosts.filter((x): x is string => typeof x === 'string') }),\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (parsed.retryable === false) retryable = false;\n\t\t\t} catch {\n\t\t\t\tmessage = text.slice(0, 300);\n\t\t\t}\n\t\t}\n\t} catch {\n\t\t// A body we cannot read is not worth failing over; the status still is.\n\t}\n\n\tconst retryAfterHeader = res.headers.get('retry-after');\n\tconst retryAfter = retryAfterHeader ? Number(retryAfterHeader) : undefined;\n\tconst requestId = res.headers.get('x-request-id') ?? undefined;\n\n\tconst init = {\n\t\tmessage: idempotencyKey ? `${message} ${turnRecoveryMessage(path, idempotencyKey)}` : message,\n\t\tcode,\n\t\tstatus: res.status,\n\t\t...(retryAfter !== undefined && Number.isFinite(retryAfter) && { retryAfter }),\n\t\t...(requestId !== undefined && { requestId }),\n\t};\n\tconst failure = hostRefusal ? new SecretHostNotAllowed({ ...init, ...hostRefusal }) : new AgentNavaError(init);\n\t// An uncertain handoff can be HTTP 503 while already owning accepted work.\n\t// Respect the server's refusal to retry instead of creating a second turn.\n\tif (retryable === false || (idempotencyKey && res.status >= 500)) Object.defineProperty(failure, 'retryable', { value: false, enumerable: true });\n\treturn failure;\n}\n\n/** Kept in errors because a dropped response may not have returned a turn ID. */\nexport function turnRecoveryMessage(path: string, idempotencyKey: string): string {\n\treturn `Check the conversation before starting another turn. To check status, POST the original body to ${path} with the same authorization, Idempotency-Key: ${idempotencyKey} and X-Turn-Status: true.`;\n}\n"]}
package/import-tools.d.ts CHANGED
@@ -199,6 +199,11 @@ export interface OpenApiDocument {
199
199
  };
200
200
  servers?: {
201
201
  url?: string;
202
+ variables?: Record<string, {
203
+ default?: string;
204
+ enum?: string[];
205
+ description?: string;
206
+ }>;
202
207
  }[];
203
208
  host?: string;
204
209
  basePath?: string;
package/import-tools.js CHANGED
@@ -68,6 +68,18 @@ const CREDENTIAL_HEADERS = new Set([
68
68
  * match is near-certain rather than a heuristic. The header rule above is what
69
69
  * does the heavy lifting. */
70
70
  const CREDENTIAL_VALUE_RE = /\b(sk-[A-Za-z0-9]{16,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|AIza[A-Za-z0-9_-]{30,}|AKIA[A-Z0-9]{16}|eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.)/;
71
+ /** The scheme and a written-out host at the start of an address: the API's
72
+ * own rule for a tool that sends a key (AgentNava ADR 0028). */
73
+ const WRITTEN_OUT_HOST = /^\s*https?:\/\/[A-Za-z0-9.-]+(?::\d{1,5})?(?=[/?#]|$)/i;
74
+ const NAMES_A_KEY = /\{\{\s*(?:secrets|secretsEncoded)\./;
75
+ /** Whether a drafted tool sends a key to an address a placeholder builds. The
76
+ * API refuses to save such a tool, because the host decides where the key
77
+ * goes, so the import refuses it first and says what to fix. A keyless tool
78
+ * may keep a placeholder, and a bare path is left for you to complete. */
79
+ function sendsKeyToPlaceholderHost(tool) {
80
+ const keyed = [tool.url, tool.body, ...Object.values(tool.headers ?? {})].some((t) => NAMES_A_KEY.test(t ?? ''));
81
+ return keyed && /^\s*(https?:\/\/|\{\{)/i.test(tool.url) && !WRITTEN_OUT_HOST.test(tool.url);
82
+ }
71
83
  /** A tool name the API will accept: `^[a-z][a-z0-9_]{0,63}$`. */
72
84
  export function toolNameFrom(raw, fallback = 'call') {
73
85
  const slug = raw
@@ -232,7 +244,12 @@ class Draft {
232
244
  `(pass \`include\` to filter during the import).`,
233
245
  });
234
246
  }
235
- return { tools: this.tools, secrets: [...this.#secrets.values()], warnings: this.warnings };
247
+ // Only the tools drafted: a refused tool's secret is not asked for.
248
+ const drafted = new Set(this.tools.map((t) => t.name));
249
+ const secrets = [...this.#secrets.values()]
250
+ .map((s) => ({ ...s, usedBy: s.usedBy.filter((name) => drafted.has(name)) }))
251
+ .filter((s) => s.usedBy.length > 0);
252
+ return { tools: this.tools, secrets, warnings: this.warnings };
236
253
  }
237
254
  }
238
255
  /** Headers with every credential replaced by a secret placeholder. */
@@ -406,6 +423,19 @@ export function fromPostman(collection, options = {}) {
406
423
  message: `Body mode "${mode}" is not supported; the tool was created without a body.`,
407
424
  });
408
425
  }
426
+ // Its base address came from a variable the collection does not hold
427
+ // (usually one kept in a Postman environment), so the host is still a
428
+ // placeholder. A tool that sends a key cannot be saved that way.
429
+ if (sendsKeyToPlaceholderHost({ url, headers, ...(body !== undefined && { body }) })) {
430
+ const variable = /^\s*(?:https?:\/\/)?[^/?#]*?\{\{\s*([^}]+?)\s*\}\}/.exec(rawUrl)?.[1] ?? 'its server';
431
+ draft.warnings.push({
432
+ kind: 'unsupported-operation',
433
+ tool: name,
434
+ message: `Skipped ${method} ${rawUrl}: This collection does not include its base address (${variable}). ` +
435
+ `Add it to the collection's variables in Postman, then import again.`,
436
+ });
437
+ continue;
438
+ }
409
439
  draft.tools.push({
410
440
  name,
411
441
  description: describe([item.description, request.description, trail.filter(Boolean).join(' / ')], name, method, rawUrl, draft),
@@ -863,7 +893,16 @@ function unlistedSecurity(doc, schemes, draft) {
863
893
  * operation's security requirement, including AND, alternatives and security:[]. */
864
894
  export function fromOpenApi(doc, options = {}) {
865
895
  const draft = new Draft();
866
- const base = doc.servers?.[0]?.url ?? (doc.host ? `${doc.schemes?.[0] ?? 'https'}://${doc.host}${doc.basePath ?? ''}` : '');
896
+ // A server variable takes its `default`, so `https://{region}.api.acme.example`
897
+ // becomes a written-out host. One with no default stays a placeholder.
898
+ const server = doc.servers?.[0];
899
+ const base = server?.url !== undefined
900
+ ? server.url.replace(/\{([^}]+)\}/g, (whole, variable) => {
901
+ const value = server.variables?.[variable]?.default;
902
+ return typeof value === 'string' && value !== '' ? value : whole;
903
+ })
904
+ : (doc.host ? `${doc.schemes?.[0] ?? 'https'}://${doc.host}${doc.basePath ?? ''}` : '');
905
+ const unresolvedServerVariable = /\{([^}]+)\}/.exec(base)?.[1];
867
906
  if (!base)
868
907
  draft.warnings.push({ kind: 'undefined-variable', message: 'The document names no server. Set absolute HTTP(S) tool URLs before creating the agent.' });
869
908
  const schemes = doc.components?.securitySchemes ?? doc.securityDefinitions ?? {};
@@ -956,6 +995,11 @@ export function fromOpenApi(doc, options = {}) {
956
995
  }
957
996
  if (query.length)
958
997
  url += '?' + query.join('&');
998
+ if (sendsKeyToPlaceholderHost({ url, headers, ...(body !== undefined && { body }) })) {
999
+ draft.warnings.push({ kind: 'unsupported-operation', tool: name, message: `Skipped ${method.toUpperCase()} ${path}: This document's server address uses {${unresolvedServerVariable ?? 'a variable'}}, which has no default value. ` +
1000
+ `Add a default to that server variable, then import again.` });
1001
+ continue;
1002
+ }
959
1003
  draft.tools.push({ name, description: describe([op.description, op.summary], name, method.toUpperCase(), path, draft), method: method.toUpperCase(), url, ...(Object.keys(headers).length && { headers }), ...(Object.keys(params).length && { params }), ...(draft.secretsFor(name).length && { secrets: draft.secretsFor(name) }), ...(body !== undefined && method !== 'get' && { body }) });
960
1004
  for (const note of ctx.notes)
961
1005
  draft.warnings.push({ kind: 'unsupported-body', tool: name, message: note });
@@ -964,10 +1008,7 @@ export function fromOpenApi(doc, options = {}) {
964
1008
  draft.warnings.push({ kind: 'unsupported-operation', tool: name, message: `Skipped ${method.toUpperCase()} ${path}: ${error.message}. Configure this operation explicitly.` });
965
1009
  }
966
1010
  }
967
- const result = draft.finish(options);
968
- const emitted = new Set(result.tools.map(t => t.name));
969
- result.secrets = result.secrets.map(s => ({ ...s, usedBy: s.usedBy.filter(name => emitted.has(name)) })).filter(s => s.usedBy.length > 0);
970
- return result;
1011
+ return draft.finish(options);
971
1012
  }
972
1013
  /**
973
1014
  * Fetch an OpenAPI document and draft tools from it.