@theokit/agents 4.8.0 → 4.9.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/dist/auth.js CHANGED
@@ -5,6 +5,38 @@ import {
5
5
  // src/auth/auth-provider.ts
6
6
  import { authFilePath, ensureFreshCredential, openaiDeviceLogin, persistOAuthTokens, readStoredOAuth } from "@theokit/sdk/auth";
7
7
  import { withFileLock } from "@theokit/sdk/persistence";
8
+ var RefreshFailure = class extends Error {
9
+ static {
10
+ __name(this, "RefreshFailure");
11
+ }
12
+ transitorio;
13
+ constructor(message, transitorio) {
14
+ super(message), this.transitorio = transitorio;
15
+ this.name = "RefreshFailure";
16
+ }
17
+ };
18
+ var MOTIVOS_TRANSITORIOS = [
19
+ "ETIMEDOUT",
20
+ "ECONNRESET",
21
+ "ECONNREFUSED",
22
+ "EAI_AGAIN",
23
+ "ENOTFOUND",
24
+ "AbortError"
25
+ ];
26
+ function classificarFalhaDeRefresh(err) {
27
+ const texto = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
28
+ if (/invalid_grant|invalid_request|unauthorized_client/i.test(texto)) {
29
+ return new RefreshFailure("o refresh token n\xE3o \xE9 mais v\xE1lido \u2014 refa\xE7a o login. Tentar de novo n\xE3o muda o resultado.", false);
30
+ }
31
+ const transitorio = MOTIVOS_TRANSITORIOS.some((m) => texto.includes(m)) || /\b(5\d{2})\b|timeout|network/i.test(texto);
32
+ return new RefreshFailure(transitorio ? "falha transit\xF3ria ao refrescar a credencial" : "falha ao refrescar a credencial", transitorio);
33
+ }
34
+ __name(classificarFalhaDeRefresh, "classificarFalhaDeRefresh");
35
+ function esperaComJitter(tentativa, baseMs = 200, aleatorio = Math.random) {
36
+ const base = baseMs * 2 ** tentativa;
37
+ return Math.round(base * (0.75 + aleatorio() * 0.5));
38
+ }
39
+ __name(esperaComJitter, "esperaComJitter");
8
40
  var AuthProvider = class _AuthProvider {
9
41
  static {
10
42
  __name(this, "AuthProvider");
@@ -56,11 +88,20 @@ var AuthProvider = class _AuthProvider {
56
88
  apiKey: doDisco.access,
57
89
  expiresAt: doDisco.expires
58
90
  } : resolved;
59
- return await ensureFreshCredential(atual, {
60
- config: this.config,
61
- store: this.store,
62
- env
63
- }, deps);
91
+ const MAX_TENTATIVAS = 3;
92
+ for (let tentativa = 0; ; tentativa++) {
93
+ try {
94
+ return await ensureFreshCredential(atual, {
95
+ config: this.config,
96
+ store: this.store,
97
+ env
98
+ }, deps);
99
+ } catch (err) {
100
+ const falha = classificarFalhaDeRefresh(err);
101
+ if (!falha.transitorio || tentativa >= MAX_TENTATIVAS - 1) throw falha;
102
+ await new Promise((resolve) => setTimeout(resolve, esperaComJitter(tentativa)));
103
+ }
104
+ }
64
105
  });
65
106
  }
66
107
  /**
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth/auth-provider.ts","../src/auth-entry.ts"],"sourcesContent":["import {\n authFilePath,\n ensureFreshCredential,\n openaiDeviceLogin,\n persistOAuthTokens,\n readStoredOAuth,\n} from '@theokit/sdk/auth'\nimport type {\n CredentialStoreConfig,\n OAuthProviderConfig,\n OAuthTokens,\n OpenAIDeviceConfig,\n ResolvedCredential,\n} from '@theokit/sdk/auth'\nimport { withFileLock } from '@theokit/sdk/persistence'\n\n/**\n * M60 — `AuthProvider`, the OO contract that unifies the SDK's free OAuth-lifecycle functions\n * (`openaiDeviceLogin` → `persistOAuthTokens` → `ensureFreshCredential`). Those are stateful across a\n * SHARED `config` (`OAuthProviderConfig`) + `store` (`CredentialStoreConfig`); this class holds that\n * state so a consumer authors `new AuthProvider(config, store).persist(...)` / `.ensureFresh(...)`\n * instead of threading `config`/`store` through every call — the `SDK → Theokit → AgentBuilder`\n * boundary applied to the auth domain (ENRICH, per blueprint D2: auth carries orchestration + state).\n *\n * It DELEGATES, never reimplements (parsimony Rung 9): each method forwards verbatim to the SDK\n * function, so login → persist → refresh produces byte-identical state to calling the SDK directly.\n *\n * SECRET-SAFETY (hard rule): this class NEVER logs, stringifies, or otherwise surfaces token material.\n * Methods return exactly what the SDK returns and add no observability — a token is data that flows\n * through, never something this layer emits. The parity/secret tests pin both properties.\n */\n\n/** The HTTP deps of `ensureFreshCredential` (`{ fetch, now }`) — typed off the SDK to avoid drift. */\ntype EnsureFreshHttpDeps = Parameters<typeof ensureFreshCredential>[2]\n/** The device-flow deps + prompt hook of `openaiDeviceLogin` — typed off the SDK. */\ntype DeviceLoginDeps = Parameters<typeof openaiDeviceLogin>[1]\ntype DeviceLoginHooks = Parameters<typeof openaiDeviceLogin>[2]\n\n/**\n * Falha de refresh, classificada — porque a decisão de tentar de novo depende da classe, não do texto.\n *\n * M74: repetir um `invalid_grant` não é resiliência, é ruído. O refresh token foi revogado e nenhuma\n * tentativa muda isso; o usuário que revogou o login espera três backoffs para ler uma mensagem que já\n * era conhecida na primeira resposta. Rede e 5xx são o oposto: quase sempre passam na segunda.\n *\n * SECRET-SAFETY: a mensagem nunca carrega material de token — só a classe e o motivo.\n */\nexport class RefreshFailure extends Error {\n constructor(\n message: string,\n /** `true` ⇒ vale tentar de novo com backoff. `false` ⇒ terminal, falha na primeira. */\n readonly transitorio: boolean,\n ) {\n super(message)\n this.name = 'RefreshFailure'\n }\n}\n\n/**\n * O que conta como transitório. Lista NOMEADA de propósito: é decisão de produto que envelhece, e\n * espalhá-la em `if` faz cada sítio envelhecer sozinho.\n */\nconst MOTIVOS_TRANSITORIOS = [\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'EAI_AGAIN',\n 'ENOTFOUND',\n 'AbortError',\n]\n\n/** Classifica uma falha de refresh. `invalid_grant` é terminal; rede e 5xx são transitórios. */\nexport function classificarFalhaDeRefresh(err: unknown): RefreshFailure {\n const texto = err instanceof Error ? `${err.name}: ${err.message}` : String(err)\n if (/invalid_grant|invalid_request|unauthorized_client/i.test(texto)) {\n return new RefreshFailure(\n 'o refresh token não é mais válido — refaça o login. Tentar de novo não muda o resultado.',\n false,\n )\n }\n const transitorio =\n MOTIVOS_TRANSITORIOS.some((m) => texto.includes(m)) ||\n /\\b(5\\d{2})\\b|timeout|network/i.test(texto)\n return new RefreshFailure(\n transitorio ? 'falha transitória ao refrescar a credencial' : 'falha ao refrescar a credencial',\n transitorio,\n )\n}\n\n/** Espera com jitter: backoff exponencial ±25%, para dois processos não retentarem em uníssono. */\nexport function esperaComJitter(tentativa: number, baseMs = 200, aleatorio = Math.random): number {\n const base = baseMs * 2 ** tentativa\n return Math.round(base * (0.75 + aleatorio() * 0.5))\n}\n\nexport class AuthProvider {\n constructor(\n private readonly config: OAuthProviderConfig,\n private readonly store: CredentialStoreConfig,\n ) {}\n\n /**\n * Refresh a resolved credential if stale. Delegates to `ensureFreshCredential` with the held\n * `config` + `store`; `env` (for reading the store's env overrides) + the `{ fetch, now }` HTTP deps\n * thread through. Returns the fresh `ResolvedCredential` — never logs the rotated token.\n */\n async ensureFresh(\n resolved: ResolvedCredential,\n deps: EnsureFreshHttpDeps,\n env?: Record<string, string | undefined>,\n ): Promise<ResolvedCredential> {\n if (resolved.kind !== 'oauth') {\n // Chave de API não expira: nenhum lock, nenhuma releitura, nenhum I/O. Manter o caminho quente\n // livre é o que torna o resolvedor por-run barato (M74, Risco 1).\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- `CredentialStoreConfig` do subpath /auth resolve sob tsc; o projeto type-aware do eslint o lê como error-typed.\n return ensureFreshCredential(resolved, { config: this.config, store: this.store, env }, deps)\n }\n\n const caminho: string = authFilePath(this.store, env)\n\n // SINGLE-FLIGHT in-process, ANTES do lock (M74, EC-2 do edge-case review).\n //\n // `withFileLock` (proper-lockfile) NÃO é reentrante: se uma run começar de dentro de um contexto\n // que já segura o lock — uma run aninhada, ou um time disparando membros enquanto o pai refresca —\n // a segunda aquisição espera até o timeout e o sintoma é \"a run travou\", sem erro nenhum. Com o\n // resolvedor por-run do M74 isso deixa de ser hipotético: `ensureFresh` passa a ser chamado de\n // dentro do stream. A promise em voo faz a reentrância resolver por composição, não por lock.\n const emVoo = AuthProvider.refreshEmVoo.get(caminho)\n if (emVoo !== undefined) return emVoo\n\n const promessa = this.refrescarSobLock(caminho, resolved, deps, env)\n AuthProvider.refreshEmVoo.set(caminho, promessa)\n try {\n return await promessa\n } finally {\n AuthProvider.refreshEmVoo.delete(caminho)\n }\n }\n\n /** Refresh em voo por caminho de store — a chave é o arquivo, não a instância. */\n private static readonly refreshEmVoo = new Map<string, Promise<ResolvedCredential>>()\n\n /**\n * O refresh propriamente dito, serializado entre PROCESSOS e com re-leitura.\n *\n * A re-leitura não é detalhe: sem ela o lock apenas serializa, e o segundo processo decide com o\n * estado que leu ANTES de esperar — refrescando de novo e invalidando o token que o primeiro acabou\n * de gravar. É o double-checked locking clássico, e é o que o teste de dois processos pega.\n */\n private refrescarSobLock(\n caminho: string,\n resolved: ResolvedCredential,\n deps: EnsureFreshHttpDeps,\n env?: Record<string, string | undefined>,\n ): Promise<ResolvedCredential> {\n /* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- os subpaths `/auth` e `/persistence` do SDK resolvem sob tsc (typecheck da raiz limpo); o projeto type-aware do eslint os lê como error-typed. Mesma nota que este arquivo já carregava para `CredentialStoreConfig`. */\n return withFileLock(caminho, async () => {\n // RE-LEITURA depois de adquirir o lock: outro processo pode ter refrescado enquanto esperávamos.\n const doDisco = readStoredOAuth(this.store, env)\n const atual =\n doDisco !== undefined\n ? { ...resolved, apiKey: doDisco.access, expiresAt: doDisco.expires }\n : resolved\n return await ensureFreshCredential(\n atual,\n { config: this.config, store: this.store, env },\n deps,\n )\n }) as Promise<ResolvedCredential>\n /* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n }\n\n /**\n * Run the headless OpenAI device-login flow. Delegates to `openaiDeviceLogin` (which JWT-extracts the\n * account id). `deviceConfig` is passed per-call because it is a distinct endpoint set from the\n * refresh `config`. Returns `OAuthTokens` — the caller persists them via {@link AuthProvider.persist}.\n */\n deviceLogin(\n deviceConfig: OpenAIDeviceConfig,\n deps: DeviceLoginDeps,\n hooks: DeviceLoginHooks,\n ): Promise<OAuthTokens> {\n return openaiDeviceLogin(deviceConfig, deps, hooks)\n }\n\n /**\n * Persist freshly-obtained tokens through the held `store`. Delegates to `persistOAuthTokens` and\n * returns the credential-file path (never the token). `env` selects the store's home override.\n */\n persist(provider: string, tokens: OAuthTokens, env?: Record<string, string | undefined>): string {\n return persistOAuthTokens(provider, tokens, this.store, env)\n }\n}\n","// M60 — `@theokit/agents/auth`: the OO auth contract. `AuthProvider` (ENRICH — it holds the shared\n// `config`+`store` state and delegates to the SDK's free OAuth-lifecycle functions), plus the auth\n// domain's types re-exported so a consumer types the whole surface from the Theokit layer, never\n// reaching back to `@theokit/sdk/auth`.\nexport { AuthProvider } from './auth/auth-provider.js'\n\n// M73 — \"enriquecer nunca reduz\". A mecânica de store do SDK atravessa como PASS-THROUGH PURO.\n//\n// Antes disto, este subpath exportava 1 valor e 6 tipos enquanto `@theokit/sdk/auth` exportava 19\n// símbolos: nenhuma função atravessava. Como o consumidor tem regra INQUEBRÁVEL de nunca importar\n// `@theokit/sdk*` direto, **reimplementar era a única saída legal** — e o agent-builder reescreveu\n// seis destes nomes, ~120 linhas de mecânica duplicada. A lacuna era daqui, não indisciplina de lá.\n//\n// PURO, e não wrapper (parsimony Rung 9): estas são funções de I/O sem estado a segurar. A camada\n// enriquece onde há orquestração — é o que `AuthProvider` faz com o par `config`+`store`. Envolver\n// isto acrescentaria indireção sem nada dentro, e **quebraria `instanceof`**: o consumidor faz\n// `err instanceof CredentialError`, o que só vale enquanto a classe for a MESMA referência do SDK.\n// `tests/unit/auth-parity.test.ts` trava essa identidade com `toBe`.\n//\n// `resolveCredential` NÃO atravessa, de propósito: o SDK e o agent-builder têm funções DIFERENTES com\n// esse nome (sync vs async, lança vs `undefined`, lê env vs não lê, infere provider vs recusa), e o\n// próprio SDK declara que a precedência de env, a inferência por prefixo e o provider declarado são\n// app policy do consumidor (`internal/auth/credential-store.ts`). Expor os dois no mesmo escopo seria\n// convite a importar o errado, com falha silenciosa.\nexport {\n authFilePath,\n CredentialError,\n credentialHome,\n readAuthFile,\n readStoredOAuth,\n writeCredential,\n} from '@theokit/sdk/auth'\nexport type { ResolveCredentialOptions } from '@theokit/sdk/auth'\nexport type {\n CredentialStoreConfig,\n DeviceDeps,\n OAuthProviderConfig,\n OAuthTokens,\n OpenAIDeviceConfig,\n ResolvedCredential,\n} from '@theokit/sdk/auth'\n"],"mappings":";;;;;AAAA,SACEA,cACAC,uBACAC,mBACAC,oBACAC,uBACK;AAQP,SAASC,oBAAoB;AAiFtB,IAAMC,eAAN,MAAMA,cAAAA;EA/Fb,OA+FaA;;;;;EACX,YACmBC,QACAC,OACjB;SAFiBD,SAAAA;SACAC,QAAAA;EAChB;;;;;;EAOH,MAAMC,YACJC,UACAC,MACAC,KAC6B;AAC7B,QAAIF,SAASG,SAAS,SAAS;AAI7B,aAAOC,sBAAsBJ,UAAU;QAAEH,QAAQ,KAAKA;QAAQC,OAAO,KAAKA;QAAOI;MAAI,GAAGD,IAAAA;IAC1F;AAEA,UAAMI,UAAkBC,aAAa,KAAKR,OAAOI,GAAAA;AASjD,UAAMK,QAAQX,cAAaY,aAAaC,IAAIJ,OAAAA;AAC5C,QAAIE,UAAUG,OAAW,QAAOH;AAEhC,UAAMI,WAAW,KAAKC,iBAAiBP,SAASL,UAAUC,MAAMC,GAAAA;AAChEN,kBAAaY,aAAaK,IAAIR,SAASM,QAAAA;AACvC,QAAI;AACF,aAAO,MAAMA;IACf,UAAA;AACEf,oBAAaY,aAAaM,OAAOT,OAAAA;IACnC;EACF;;EAGA,OAAwBG,eAAe,oBAAIO,IAAAA;;;;;;;;EASnCH,iBACNP,SACAL,UACAC,MACAC,KAC6B;AAE7B,WAAOc,aAAaX,SAAS,YAAA;AAE3B,YAAMY,UAAUC,gBAAgB,KAAKpB,OAAOI,GAAAA;AAC5C,YAAMiB,QACJF,YAAYP,SACR;QAAE,GAAGV;QAAUoB,QAAQH,QAAQI;QAAQC,WAAWL,QAAQM;MAAQ,IAClEvB;AACN,aAAO,MAAMI,sBACXe,OACA;QAAEtB,QAAQ,KAAKA;QAAQC,OAAO,KAAKA;QAAOI;MAAI,GAC9CD,IAAAA;IAEJ,CAAA;EAEF;;;;;;EAOAuB,YACEC,cACAxB,MACAyB,OACsB;AACtB,WAAOC,kBAAkBF,cAAcxB,MAAMyB,KAAAA;EAC/C;;;;;EAMAE,QAAQC,UAAkBC,QAAqB5B,KAAkD;AAC/F,WAAO6B,mBAAmBF,UAAUC,QAAQ,KAAKhC,OAAOI,GAAAA;EAC1D;AACF;;;ACxKA,SACE8B,gBAAAA,eACAC,iBACAC,gBACAC,cACAC,mBAAAA,kBACAC,uBACK;","names":["authFilePath","ensureFreshCredential","openaiDeviceLogin","persistOAuthTokens","readStoredOAuth","withFileLock","AuthProvider","config","store","ensureFresh","resolved","deps","env","kind","ensureFreshCredential","caminho","authFilePath","emVoo","refreshEmVoo","get","undefined","promessa","refrescarSobLock","set","delete","Map","withFileLock","doDisco","readStoredOAuth","atual","apiKey","access","expiresAt","expires","deviceLogin","deviceConfig","hooks","openaiDeviceLogin","persist","provider","tokens","persistOAuthTokens","authFilePath","CredentialError","credentialHome","readAuthFile","readStoredOAuth","writeCredential"]}
1
+ {"version":3,"sources":["../src/auth/auth-provider.ts","../src/auth-entry.ts"],"sourcesContent":["import {\n authFilePath,\n ensureFreshCredential,\n openaiDeviceLogin,\n persistOAuthTokens,\n readStoredOAuth,\n} from '@theokit/sdk/auth'\nimport type {\n CredentialStoreConfig,\n OAuthProviderConfig,\n OAuthTokens,\n OpenAIDeviceConfig,\n ResolvedCredential,\n} from '@theokit/sdk/auth'\nimport { withFileLock } from '@theokit/sdk/persistence'\n\n/**\n * M60 — `AuthProvider`, the OO contract that unifies the SDK's free OAuth-lifecycle functions\n * (`openaiDeviceLogin` → `persistOAuthTokens` → `ensureFreshCredential`). Those are stateful across a\n * SHARED `config` (`OAuthProviderConfig`) + `store` (`CredentialStoreConfig`); this class holds that\n * state so a consumer authors `new AuthProvider(config, store).persist(...)` / `.ensureFresh(...)`\n * instead of threading `config`/`store` through every call — the `SDK → Theokit → AgentBuilder`\n * boundary applied to the auth domain (ENRICH, per blueprint D2: auth carries orchestration + state).\n *\n * It DELEGATES, never reimplements (parsimony Rung 9): each method forwards verbatim to the SDK\n * function, so login → persist → refresh produces byte-identical state to calling the SDK directly.\n *\n * SECRET-SAFETY (hard rule): this class NEVER logs, stringifies, or otherwise surfaces token material.\n * Methods return exactly what the SDK returns and add no observability — a token is data that flows\n * through, never something this layer emits. The parity/secret tests pin both properties.\n */\n\n/** The HTTP deps of `ensureFreshCredential` (`{ fetch, now }`) — typed off the SDK to avoid drift. */\ntype EnsureFreshHttpDeps = Parameters<typeof ensureFreshCredential>[2]\n/** The device-flow deps + prompt hook of `openaiDeviceLogin` — typed off the SDK. */\ntype DeviceLoginDeps = Parameters<typeof openaiDeviceLogin>[1]\ntype DeviceLoginHooks = Parameters<typeof openaiDeviceLogin>[2]\n\n/**\n * Falha de refresh, classificada — porque a decisão de tentar de novo depende da classe, não do texto.\n *\n * M74: repetir um `invalid_grant` não é resiliência, é ruído. O refresh token foi revogado e nenhuma\n * tentativa muda isso; o usuário que revogou o login espera três backoffs para ler uma mensagem que já\n * era conhecida na primeira resposta. Rede e 5xx são o oposto: quase sempre passam na segunda.\n *\n * SECRET-SAFETY: a mensagem nunca carrega material de token — só a classe e o motivo.\n */\nexport class RefreshFailure extends Error {\n constructor(\n message: string,\n /** `true` ⇒ vale tentar de novo com backoff. `false` ⇒ terminal, falha na primeira. */\n readonly transitorio: boolean,\n ) {\n super(message)\n this.name = 'RefreshFailure'\n }\n}\n\n/**\n * O que conta como transitório. Lista NOMEADA de propósito: é decisão de produto que envelhece, e\n * espalhá-la em `if` faz cada sítio envelhecer sozinho.\n */\nconst MOTIVOS_TRANSITORIOS = [\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'EAI_AGAIN',\n 'ENOTFOUND',\n 'AbortError',\n]\n\n/** Classifica uma falha de refresh. `invalid_grant` é terminal; rede e 5xx são transitórios. */\nexport function classificarFalhaDeRefresh(err: unknown): RefreshFailure {\n const texto = err instanceof Error ? `${err.name}: ${err.message}` : String(err)\n if (/invalid_grant|invalid_request|unauthorized_client/i.test(texto)) {\n return new RefreshFailure(\n 'o refresh token não é mais válido — refaça o login. Tentar de novo não muda o resultado.',\n false,\n )\n }\n const transitorio =\n MOTIVOS_TRANSITORIOS.some((m) => texto.includes(m)) ||\n /\\b(5\\d{2})\\b|timeout|network/i.test(texto)\n return new RefreshFailure(\n transitorio ? 'falha transitória ao refrescar a credencial' : 'falha ao refrescar a credencial',\n transitorio,\n )\n}\n\n/** Espera com jitter: backoff exponencial ±25%, para dois processos não retentarem em uníssono. */\nexport function esperaComJitter(tentativa: number, baseMs = 200, aleatorio = Math.random): number {\n const base = baseMs * 2 ** tentativa\n return Math.round(base * (0.75 + aleatorio() * 0.5))\n}\n\nexport class AuthProvider {\n constructor(\n private readonly config: OAuthProviderConfig,\n private readonly store: CredentialStoreConfig,\n ) {}\n\n /**\n * Refresh a resolved credential if stale. Delegates to `ensureFreshCredential` with the held\n * `config` + `store`; `env` (for reading the store's env overrides) + the `{ fetch, now }` HTTP deps\n * thread through. Returns the fresh `ResolvedCredential` — never logs the rotated token.\n */\n async ensureFresh(\n resolved: ResolvedCredential,\n deps: EnsureFreshHttpDeps,\n env?: Record<string, string | undefined>,\n ): Promise<ResolvedCredential> {\n if (resolved.kind !== 'oauth') {\n // Chave de API não expira: nenhum lock, nenhuma releitura, nenhum I/O. Manter o caminho quente\n // livre é o que torna o resolvedor por-run barato (M74, Risco 1).\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- `CredentialStoreConfig` do subpath /auth resolve sob tsc; o projeto type-aware do eslint o lê como error-typed.\n return ensureFreshCredential(resolved, { config: this.config, store: this.store, env }, deps)\n }\n\n const caminho: string = authFilePath(this.store, env)\n\n // SINGLE-FLIGHT in-process, ANTES do lock (M74, EC-2 do edge-case review).\n //\n // `withFileLock` (proper-lockfile) NÃO é reentrante: se uma run começar de dentro de um contexto\n // que já segura o lock — uma run aninhada, ou um time disparando membros enquanto o pai refresca —\n // a segunda aquisição espera até o timeout e o sintoma é \"a run travou\", sem erro nenhum. Com o\n // resolvedor por-run do M74 isso deixa de ser hipotético: `ensureFresh` passa a ser chamado de\n // dentro do stream. A promise em voo faz a reentrância resolver por composição, não por lock.\n const emVoo = AuthProvider.refreshEmVoo.get(caminho)\n if (emVoo !== undefined) return emVoo\n\n const promessa = this.refrescarSobLock(caminho, resolved, deps, env)\n AuthProvider.refreshEmVoo.set(caminho, promessa)\n try {\n return await promessa\n } finally {\n AuthProvider.refreshEmVoo.delete(caminho)\n }\n }\n\n /** Refresh em voo por caminho de store — a chave é o arquivo, não a instância. */\n private static readonly refreshEmVoo = new Map<string, Promise<ResolvedCredential>>()\n\n /**\n * O refresh propriamente dito, serializado entre PROCESSOS e com re-leitura.\n *\n * A re-leitura não é detalhe: sem ela o lock apenas serializa, e o segundo processo decide com o\n * estado que leu ANTES de esperar — refrescando de novo e invalidando o token que o primeiro acabou\n * de gravar. É o double-checked locking clássico, e é o que o teste de dois processos pega.\n */\n private refrescarSobLock(\n caminho: string,\n resolved: ResolvedCredential,\n deps: EnsureFreshHttpDeps,\n env?: Record<string, string | undefined>,\n ): Promise<ResolvedCredential> {\n /* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access -- os subpaths `/auth` e `/persistence` do SDK resolvem sob tsc (typecheck da raiz limpo); o projeto type-aware do eslint os lê como error-typed. Mesma nota que este arquivo já carregava para `CredentialStoreConfig`. */\n return withFileLock(caminho, async () => {\n // RE-LEITURA depois de adquirir o lock: outro processo pode ter refrescado enquanto esperávamos.\n const doDisco = readStoredOAuth(this.store, env)\n const atual =\n doDisco !== undefined\n ? { ...resolved, apiKey: doDisco.access, expiresAt: doDisco.expires }\n : resolved\n // Retry SÓ no transitório. Um `invalid_grant` falha na PRIMEIRA: o token foi revogado e nenhuma\n // tentativa muda isso — insistir só atrasa a mensagem que o usuário precisa ler.\n //\n // Este laço já existiu e foi APAGADO por um lint-fix meu que reescreveu o bloco inteiro; o\n // review pegou (`tentativas de POST = 1`, contra as 3 que a DoD exige). Testar o classificador\n // isolado prova que ele classifica, não que está LIGADO — por isso há um gate estrutural.\n const MAX_TENTATIVAS = 3\n for (let tentativa = 0; ; tentativa++) {\n try {\n return await ensureFreshCredential(\n atual,\n { config: this.config, store: this.store, env },\n deps,\n )\n } catch (err) {\n const falha = classificarFalhaDeRefresh(err)\n if (!falha.transitorio || tentativa >= MAX_TENTATIVAS - 1) throw falha\n await new Promise((resolve) => setTimeout(resolve, esperaComJitter(tentativa)))\n }\n }\n }) as Promise<ResolvedCredential>\n /* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */\n }\n\n /**\n * Run the headless OpenAI device-login flow. Delegates to `openaiDeviceLogin` (which JWT-extracts the\n * account id). `deviceConfig` is passed per-call because it is a distinct endpoint set from the\n * refresh `config`. Returns `OAuthTokens` — the caller persists them via {@link AuthProvider.persist}.\n */\n deviceLogin(\n deviceConfig: OpenAIDeviceConfig,\n deps: DeviceLoginDeps,\n hooks: DeviceLoginHooks,\n ): Promise<OAuthTokens> {\n return openaiDeviceLogin(deviceConfig, deps, hooks)\n }\n\n /**\n * Persist freshly-obtained tokens through the held `store`. Delegates to `persistOAuthTokens` and\n * returns the credential-file path (never the token). `env` selects the store's home override.\n */\n persist(provider: string, tokens: OAuthTokens, env?: Record<string, string | undefined>): string {\n return persistOAuthTokens(provider, tokens, this.store, env)\n }\n}\n","// M60 — `@theokit/agents/auth`: the OO auth contract. `AuthProvider` (ENRICH — it holds the shared\n// `config`+`store` state and delegates to the SDK's free OAuth-lifecycle functions), plus the auth\n// domain's types re-exported so a consumer types the whole surface from the Theokit layer, never\n// reaching back to `@theokit/sdk/auth`.\nexport { AuthProvider } from './auth/auth-provider.js'\n\n// M73 — \"enriquecer nunca reduz\". A mecânica de store do SDK atravessa como PASS-THROUGH PURO.\n//\n// Antes disto, este subpath exportava 1 valor e 6 tipos enquanto `@theokit/sdk/auth` exportava 19\n// símbolos: nenhuma função atravessava. Como o consumidor tem regra INQUEBRÁVEL de nunca importar\n// `@theokit/sdk*` direto, **reimplementar era a única saída legal** — e o agent-builder reescreveu\n// seis destes nomes, ~120 linhas de mecânica duplicada. A lacuna era daqui, não indisciplina de lá.\n//\n// PURO, e não wrapper (parsimony Rung 9): estas são funções de I/O sem estado a segurar. A camada\n// enriquece onde há orquestração — é o que `AuthProvider` faz com o par `config`+`store`. Envolver\n// isto acrescentaria indireção sem nada dentro, e **quebraria `instanceof`**: o consumidor faz\n// `err instanceof CredentialError`, o que só vale enquanto a classe for a MESMA referência do SDK.\n// `tests/unit/auth-parity.test.ts` trava essa identidade com `toBe`.\n//\n// `resolveCredential` NÃO atravessa, de propósito: o SDK e o agent-builder têm funções DIFERENTES com\n// esse nome (sync vs async, lança vs `undefined`, lê env vs não lê, infere provider vs recusa), e o\n// próprio SDK declara que a precedência de env, a inferência por prefixo e o provider declarado são\n// app policy do consumidor (`internal/auth/credential-store.ts`). Expor os dois no mesmo escopo seria\n// convite a importar o errado, com falha silenciosa.\nexport {\n authFilePath,\n CredentialError,\n credentialHome,\n readAuthFile,\n readStoredOAuth,\n writeCredential,\n} from '@theokit/sdk/auth'\nexport type { ResolveCredentialOptions } from '@theokit/sdk/auth'\nexport type {\n CredentialStoreConfig,\n DeviceDeps,\n OAuthProviderConfig,\n OAuthTokens,\n OpenAIDeviceConfig,\n ResolvedCredential,\n} from '@theokit/sdk/auth'\n"],"mappings":";;;;;AAAA,SACEA,cACAC,uBACAC,mBACAC,oBACAC,uBACK;AAQP,SAASC,oBAAoB;AAiCtB,IAAMC,iBAAN,cAA6BC,MAAAA;EA/CpC,OA+CoCA;;;;EAClC,YACEC,SAESC,aACT;AACA,UAAMD,OAAAA,GAAAA,KAFGC,cAAAA;AAGT,SAAKC,OAAO;EACd;AACF;AAMA,IAAMC,uBAAuB;EAC3B;EACA;EACA;EACA;EACA;EACA;;AAIK,SAASC,0BAA0BC,KAAY;AACpD,QAAMC,QAAQD,eAAeN,QAAQ,GAAGM,IAAIH,IAAI,KAAKG,IAAIL,OAAO,KAAKO,OAAOF,GAAAA;AAC5E,MAAI,qDAAqDG,KAAKF,KAAAA,GAAQ;AACpE,WAAO,IAAIR,eACT,gHACA,KAAA;EAEJ;AACA,QAAMG,cACJE,qBAAqBM,KAAK,CAACC,MAAMJ,MAAMK,SAASD,CAAAA,CAAAA,KAChD,gCAAgCF,KAAKF,KAAAA;AACvC,SAAO,IAAIR,eACTG,cAAc,mDAAgD,mCAC9DA,WAAAA;AAEJ;AAfgBG;AAkBT,SAASQ,gBAAgBC,WAAmBC,SAAS,KAAKC,YAAYC,KAAKC,QAAM;AACtF,QAAMC,OAAOJ,SAAS,KAAKD;AAC3B,SAAOG,KAAKG,MAAMD,QAAQ,OAAOH,UAAAA,IAAc,IAAE;AACnD;AAHgBH;AAKT,IAAMQ,eAAN,MAAMA,cAAAA;EA/Fb,OA+FaA;;;;;EACX,YACmBC,QACAC,OACjB;SAFiBD,SAAAA;SACAC,QAAAA;EAChB;;;;;;EAOH,MAAMC,YACJC,UACAC,MACAC,KAC6B;AAC7B,QAAIF,SAASG,SAAS,SAAS;AAI7B,aAAOC,sBAAsBJ,UAAU;QAAEH,QAAQ,KAAKA;QAAQC,OAAO,KAAKA;QAAOI;MAAI,GAAGD,IAAAA;IAC1F;AAEA,UAAMI,UAAkBC,aAAa,KAAKR,OAAOI,GAAAA;AASjD,UAAMK,QAAQX,cAAaY,aAAaC,IAAIJ,OAAAA;AAC5C,QAAIE,UAAUG,OAAW,QAAOH;AAEhC,UAAMI,WAAW,KAAKC,iBAAiBP,SAASL,UAAUC,MAAMC,GAAAA;AAChEN,kBAAaY,aAAaK,IAAIR,SAASM,QAAAA;AACvC,QAAI;AACF,aAAO,MAAMA;IACf,UAAA;AACEf,oBAAaY,aAAaM,OAAOT,OAAAA;IACnC;EACF;;EAGA,OAAwBG,eAAe,oBAAIO,IAAAA;;;;;;;;EASnCH,iBACNP,SACAL,UACAC,MACAC,KAC6B;AAE7B,WAAOc,aAAaX,SAAS,YAAA;AAE3B,YAAMY,UAAUC,gBAAgB,KAAKpB,OAAOI,GAAAA;AAC5C,YAAMiB,QACJF,YAAYP,SACR;QAAE,GAAGV;QAAUoB,QAAQH,QAAQI;QAAQC,WAAWL,QAAQM;MAAQ,IAClEvB;AAON,YAAMwB,iBAAiB;AACvB,eAASnC,YAAY,KAAKA,aAAa;AACrC,YAAI;AACF,iBAAO,MAAMe,sBACXe,OACA;YAAEtB,QAAQ,KAAKA;YAAQC,OAAO,KAAKA;YAAOI;UAAI,GAC9CD,IAAAA;QAEJ,SAASpB,KAAK;AACZ,gBAAM4C,QAAQ7C,0BAA0BC,GAAAA;AACxC,cAAI,CAAC4C,MAAMhD,eAAeY,aAAamC,iBAAiB,EAAG,OAAMC;AACjE,gBAAM,IAAIC,QAAQ,CAACC,YAAYC,WAAWD,SAASvC,gBAAgBC,SAAAA,CAAAA,CAAAA;QACrE;MACF;IACF,CAAA;EAEF;;;;;;EAOAwC,YACEC,cACA7B,MACA8B,OACsB;AACtB,WAAOC,kBAAkBF,cAAc7B,MAAM8B,KAAAA;EAC/C;;;;;EAMAE,QAAQC,UAAkBC,QAAqBjC,KAAkD;AAC/F,WAAOkC,mBAAmBF,UAAUC,QAAQ,KAAKrC,OAAOI,GAAAA;EAC1D;AACF;;;ACvLA,SACEmC,gBAAAA,eACAC,iBACAC,gBACAC,cACAC,mBAAAA,kBACAC,uBACK;","names":["authFilePath","ensureFreshCredential","openaiDeviceLogin","persistOAuthTokens","readStoredOAuth","withFileLock","RefreshFailure","Error","message","transitorio","name","MOTIVOS_TRANSITORIOS","classificarFalhaDeRefresh","err","texto","String","test","some","m","includes","esperaComJitter","tentativa","baseMs","aleatorio","Math","random","base","round","AuthProvider","config","store","ensureFresh","resolved","deps","env","kind","ensureFreshCredential","caminho","authFilePath","emVoo","refreshEmVoo","get","undefined","promessa","refrescarSobLock","set","delete","Map","withFileLock","doDisco","readStoredOAuth","atual","apiKey","access","expiresAt","expires","MAX_TENTATIVAS","falha","Promise","resolve","setTimeout","deviceLogin","deviceConfig","hooks","openaiDeviceLogin","persist","provider","tokens","persistOAuthTokens","authFilePath","CredentialError","credentialHome","readAuthFile","readStoredOAuth","writeCredential"]}
@@ -938,12 +938,6 @@ interface RuntimeOverrides {
938
938
  */
939
939
  sdkTools?: readonly CustomTool[];
940
940
  }
941
- /**
942
- * Creates an agent stream factory using @theokit/sdk as the runtime.
943
- *
944
- * Returns a function that, given a message + sessionId, yields TheoKit
945
- * AgentStreamEvent via the SDK's Agent.create() + Run.stream() pipeline.
946
- */
947
941
  declare function createSdkAgentStream(compiled: CompiledAgentOptions, compiledTools: CompiledTool[], apiKey: string | (() => string | Promise<string>), overrides?: RuntimeOverrides): ((message: string, sessionId: string, factoryOpts?: {
948
942
  disableTools?: boolean;
949
943
  }) => AsyncIterable<StreamEvent>) & {
@@ -973,7 +967,7 @@ interface SdkAgentHandle {
973
967
  * surface (e.g. the ACP client) owns approval. Tools still execute; they are simply not HITL-gated here.
974
968
  */
975
969
  declare function toAgentFactory(def: AgentDefinition, opts: {
976
- apiKey: string;
970
+ apiKey: string | (() => string | Promise<string>);
977
971
  overrides?: RuntimeOverrides;
978
972
  }): (sessionId: string) => Promise<SdkAgentHandle>;
979
973
 
package/dist/bridge.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { g as AGENT_BRAND, h as AfterToolCallContext, i as AgentBuilder, j as AgentDefinition, k as AgentDefinitionError, l as AgentExecutionContext, m as AgentManifest, f as AgentManifestEntry, n as AgentManifestSource, o as AgentManifestTool, q as AgentRoute, r as AgentRouteContext, s as AgentRunInfo, t as AgentStreamEvent, u as AgentTurnMetadata, v as AgentsPluginOptions, w as ApiErrorContext, x as ApiErrorDecision, y as ApiErrorPolicy, z as ApprovalRequiredEvent, B as ArtifactChunkEvent, E as ArtifactStartEvent, F as BackgroundDelegation, I as BeforeToolCallContext, J as BudgetExceededError, N as CheckpointSavedEvent, C as CompiledAgentOptions, O as CompiledContextWindow, a as CompiledTool, P as ContextualTool, V as DefineAgentConfig, W as DelegateFn, X as DelegateOptions, Y as DelegationError, D as DelegationResult, Z as DoneEvent, _ as ErrorEvent, $ as FileEditEvent, a5 as InferAgentInput, a6 as InferAgentToolNames, a7 as IterationEvent, a8 as LLMCallContext, ad as McpApprovalSpec, ae as McpRegistryConfig, af as McpRequestContext, ag as McpSelection, ah as PartialToolCallEvent, aj as ProcessInputContext, an as RunStartedEvent, ao as ScoreVerdict, ap as ScoredDelegation, aq as Scorer, ar as SdkAgentHandle, as as SdkMessage, at as Segment, aw as StateUpdateEvent, S as StreamEvent, ax as TextDeltaEvent, ay as ThinkingEvent, aA as ToolCallEvent, aB as ToolCallVeto, aC as ToolHooks, aD as ToolHooksPlugin, aE as ToolResultEvent, aF as ToolWalkResult, aH as ToolboxWalkResult, aI as agentsPlugin, aJ as buildModelSelection, aK as compileAgentDefinition, aL as compileAgentModule, aM as compileContextWindow, aN as compileProjectContext, aO as compileSkills, aP as compileTools, aQ as createAgentExecutionContext, aR as createApiErrorHandler, aS as createSdkAgentStream, aT as createThinkTagExtractor, aU as createToolHooksPlugin, aV as delegate, aW as delegateBackground, aX as delegateWithScoring, aY as extractThinkTagStream, aZ as generateAgentManifest, a_ as generateAgentRoutes, a$ as isAgentContext, b0 as isAgentDefinition, b1 as isApprovalRequired, b2 as isDone, b3 as isError, b4 as isPartialToolCall, b5 as isTextDelta, b6 as isToolCall, b7 as isToolResult, ba as mcpRegistry, bb as mcpToolApprovals, bd as presentUIMessageStream, be as projectContextMetadataOnlyKnobs, bi as resolveMcpServers, bj as runWithApiErrorHandling, bk as streamAgentResponse, bl as streamAgentUIMessages, bm as toAgentFactory, bn as translateSdkEvent } from './bridge-entry-CkpzmeJU.js';
1
+ export { g as AGENT_BRAND, h as AfterToolCallContext, i as AgentBuilder, j as AgentDefinition, k as AgentDefinitionError, l as AgentExecutionContext, m as AgentManifest, f as AgentManifestEntry, n as AgentManifestSource, o as AgentManifestTool, q as AgentRoute, r as AgentRouteContext, s as AgentRunInfo, t as AgentStreamEvent, u as AgentTurnMetadata, v as AgentsPluginOptions, w as ApiErrorContext, x as ApiErrorDecision, y as ApiErrorPolicy, z as ApprovalRequiredEvent, B as ArtifactChunkEvent, E as ArtifactStartEvent, F as BackgroundDelegation, I as BeforeToolCallContext, J as BudgetExceededError, N as CheckpointSavedEvent, C as CompiledAgentOptions, O as CompiledContextWindow, a as CompiledTool, P as ContextualTool, V as DefineAgentConfig, W as DelegateFn, X as DelegateOptions, Y as DelegationError, D as DelegationResult, Z as DoneEvent, _ as ErrorEvent, $ as FileEditEvent, a5 as InferAgentInput, a6 as InferAgentToolNames, a7 as IterationEvent, a8 as LLMCallContext, ad as McpApprovalSpec, ae as McpRegistryConfig, af as McpRequestContext, ag as McpSelection, ah as PartialToolCallEvent, aj as ProcessInputContext, an as RunStartedEvent, ao as ScoreVerdict, ap as ScoredDelegation, aq as Scorer, ar as SdkAgentHandle, as as SdkMessage, at as Segment, aw as StateUpdateEvent, S as StreamEvent, ax as TextDeltaEvent, ay as ThinkingEvent, aA as ToolCallEvent, aB as ToolCallVeto, aC as ToolHooks, aD as ToolHooksPlugin, aE as ToolResultEvent, aF as ToolWalkResult, aH as ToolboxWalkResult, aI as agentsPlugin, aJ as buildModelSelection, aK as compileAgentDefinition, aL as compileAgentModule, aM as compileContextWindow, aN as compileProjectContext, aO as compileSkills, aP as compileTools, aQ as createAgentExecutionContext, aR as createApiErrorHandler, aS as createSdkAgentStream, aT as createThinkTagExtractor, aU as createToolHooksPlugin, aV as delegate, aW as delegateBackground, aX as delegateWithScoring, aY as extractThinkTagStream, aZ as generateAgentManifest, a_ as generateAgentRoutes, a$ as isAgentContext, b0 as isAgentDefinition, b1 as isApprovalRequired, b2 as isDone, b3 as isError, b4 as isPartialToolCall, b5 as isTextDelta, b6 as isToolCall, b7 as isToolResult, ba as mcpRegistry, bb as mcpToolApprovals, bd as presentUIMessageStream, be as projectContextMetadataOnlyKnobs, bi as resolveMcpServers, bj as runWithApiErrorHandling, bk as streamAgentResponse, bl as streamAgentUIMessages, bm as toAgentFactory, bn as translateSdkEvent } from './bridge-entry-BmPk3TPf.js';
2
2
  import '@theokit/http';
3
3
  import '@theokit/sdk';
4
4
  import 'zod';
package/dist/bridge.js CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  streamAgentUIMessages,
44
44
  toAgentFactory,
45
45
  translateSdkEvent
46
- } from "./chunk-LEHFFIEO.js";
46
+ } from "./chunk-ITHCIDRN.js";
47
47
  import "./chunk-7QVYU63E.js";
48
48
  export {
49
49
  AGENT_BRAND,
@@ -1164,6 +1164,7 @@ function buildSdkTools(compiledTools, defineTool, extraSdkTools = [], runContext
1164
1164
  ];
1165
1165
  }
1166
1166
  __name(buildSdkTools, "buildSdkTools");
1167
+ var resolverApiKey = /* @__PURE__ */ __name(async (k) => typeof k === "function" ? await k() : k, "resolverApiKey");
1167
1168
  function createSdkAgentStream(compiled, compiledTools, apiKey, overrides = {}) {
1168
1169
  const model = overrides.model ?? compiled.model ?? "openai/gpt-4o-mini";
1169
1170
  const reasoningEffort = overrides.reasoningEffort ?? compiled.reasoningEffort;
@@ -1201,9 +1202,8 @@ function createSdkAgentStream(compiled, compiledTools, apiKey, overrides = {}) {
1201
1202
  keys: runContext !== void 0 ? Object.keys(runContext) : []
1202
1203
  });
1203
1204
  try {
1204
- const apiKeyResolvida = typeof apiKey === "function" ? await apiKey() : apiKey;
1205
1205
  yield* streamSdkAgent(rt, compiled, sdkTools, {
1206
- apiKey: apiKeyResolvida,
1206
+ apiKey: await resolverApiKey(apiKey),
1207
1207
  model,
1208
1208
  reasoningEffort,
1209
1209
  overrides,
@@ -1312,7 +1312,7 @@ function toAgentFactory(def, opts) {
1312
1312
  };
1313
1313
  const extra = buildExtraCreateOptions(overrides, compiled);
1314
1314
  const agent = await rt.Agent.getOrCreate(sessionId, {
1315
- apiKey: opts.apiKey,
1315
+ apiKey: await resolverApiKey(opts.apiKey),
1316
1316
  model: buildModelSelection(model, reasoningEffort),
1317
1317
  tools: sdkTools,
1318
1318
  ...m8,
@@ -3506,4 +3506,4 @@ export {
3506
3506
  generateAgentManifest,
3507
3507
  agentsPlugin
3508
3508
  };
3509
- //# sourceMappingURL=chunk-LEHFFIEO.js.map
3509
+ //# sourceMappingURL=chunk-ITHCIDRN.js.map