@theokit/sdk 4.11.0 → 4.12.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/dist/eval.cjs CHANGED
@@ -569,10 +569,10 @@ function buildToolPrompt(prompt) {
569
569
  Respond by calling the \`output\` tool with the structured answer that matches the schema.`;
570
570
  }
571
571
  function setupStructuredOutput(schema, maxRetries) {
572
- const z9 = requireZod();
572
+ const z10 = requireZod();
573
573
  const jsonSchema = toJsonSchema(schema, { unrepresentable: "any" });
574
574
  return {
575
- z: z9,
575
+ z: z10,
576
576
  jsonSchema,
577
577
  maxRetries: maxRetries ?? 1,
578
578
  initialUsage: { inputTokens: 0, outputTokens: 0 }
@@ -11374,6 +11374,358 @@ var OPENAI = {
11374
11374
  hostname: "api.openai.com",
11375
11375
  fallbackModels: ["gpt-4o", "gpt-4o-mini"]
11376
11376
  };
11377
+ function credentialHome(config, env = {}) {
11378
+ const override = config.homeEnvVar !== void 0 ? env[config.homeEnvVar]?.trim() : void 0;
11379
+ return override !== void 0 && override.length > 0 ? override : path.join(config.home, config.dirName);
11380
+ }
11381
+ function authFilePath(config, env = {}) {
11382
+ return path.join(credentialHome(config, env), config.fileName);
11383
+ }
11384
+ var CredentialError = class extends Error {
11385
+ constructor(message) {
11386
+ super(message);
11387
+ this.name = "CredentialError";
11388
+ }
11389
+ };
11390
+ var apiFileSchema = zod.z.object({
11391
+ type: zod.z.literal("api").optional(),
11392
+ provider: zod.z.string().min(1).optional(),
11393
+ api_key: zod.z.string()
11394
+ }).strict();
11395
+ var oauthFileSchema = zod.z.object({
11396
+ type: zod.z.literal("oauth"),
11397
+ provider: zod.z.string().min(1),
11398
+ access: zod.z.string().min(1),
11399
+ refresh: zod.z.string().min(1),
11400
+ expires: zod.z.number(),
11401
+ account_id: zod.z.string().optional()
11402
+ }).strict();
11403
+ var fileSchema = zod.z.union([oauthFileSchema, apiFileSchema]);
11404
+ function assertSecureModes(dirPath, path) {
11405
+ const dirMode = fs.statSync(dirPath).mode & 511;
11406
+ if ((dirMode & 18) !== 0) {
11407
+ throw new CredentialError(
11408
+ `${dirPath} is writable by other users (mode ${dirMode.toString(8)}), so the credential file inside it can be replaced. Fix it with: chmod 700 ${dirPath}`
11409
+ );
11410
+ }
11411
+ const mode = fs.statSync(path).mode & 511;
11412
+ if ((mode & 63) !== 0) {
11413
+ throw new CredentialError(
11414
+ `${path} is readable by other users (mode ${mode.toString(8)}). A credential file must not be. Fix it with: chmod 600 ${path}`
11415
+ );
11416
+ }
11417
+ }
11418
+ function describeUnionError(parsed, err, path) {
11419
+ const looksOAuth = typeof parsed === "object" && parsed !== null && parsed.type === "oauth";
11420
+ const specific = looksOAuth ? oauthFileSchema.safeParse(parsed) : apiFileSchema.safeParse(parsed);
11421
+ let issue;
11422
+ if (!specific.success) {
11423
+ issue = specific.error.issues[0];
11424
+ } else if (err instanceof zod.z.ZodError) {
11425
+ issue = err.issues[0];
11426
+ }
11427
+ return new CredentialError(
11428
+ `${path}: ${issue?.message ?? String(err)} [${issue?.path.join(".") || "root"}]`
11429
+ );
11430
+ }
11431
+ function parseStoredFile(raw, path) {
11432
+ let parsed;
11433
+ try {
11434
+ parsed = JSON.parse(raw);
11435
+ } catch {
11436
+ throw new CredentialError(
11437
+ `${path} is not valid JSON. Expected: {"provider": "<name>", "api_key": "..."}`
11438
+ );
11439
+ }
11440
+ try {
11441
+ return fileSchema.parse(parsed);
11442
+ } catch (err) {
11443
+ throw describeUnionError(parsed, err, path);
11444
+ }
11445
+ }
11446
+ function readAuthFile(config, env = {}) {
11447
+ const path = authFilePath(config, env);
11448
+ let raw;
11449
+ try {
11450
+ raw = fs.readFileSync(path, "utf8");
11451
+ } catch (err) {
11452
+ if (err.code === "ENOENT") return void 0;
11453
+ throw new CredentialError(`cannot read ${path}: ${err.message}`);
11454
+ }
11455
+ assertSecureModes(credentialHome(config, env), path);
11456
+ return parseStoredFile(raw, path);
11457
+ }
11458
+ function readStoredOAuth(config, env = {}) {
11459
+ const stored = readAuthFile(config, env);
11460
+ return stored !== void 0 && stored.type === "oauth" ? stored : void 0;
11461
+ }
11462
+ function isOAuthWrite(c) {
11463
+ return "type" in c && c.type === "oauth";
11464
+ }
11465
+ function buildStorePayload(cred) {
11466
+ if (isOAuthWrite(cred)) {
11467
+ if (cred.access.length === 0 || cred.refresh.length === 0) {
11468
+ throw new CredentialError(
11469
+ "refusing to write an oauth credential with an empty access/refresh token"
11470
+ );
11471
+ }
11472
+ return {
11473
+ type: "oauth",
11474
+ provider: cred.provider,
11475
+ access: cred.access,
11476
+ refresh: cred.refresh,
11477
+ expires: cred.expires,
11478
+ ...cred.account_id !== void 0 ? { account_id: cred.account_id } : {}
11479
+ };
11480
+ }
11481
+ if (typeof cred.apiKey !== "string" || cred.apiKey.length === 0) {
11482
+ throw new CredentialError("refusing to write an empty API key");
11483
+ }
11484
+ return { provider: cred.provider, api_key: cred.apiKey };
11485
+ }
11486
+ function writeCredential(cred, config, env = {}) {
11487
+ const payload = buildStorePayload(cred);
11488
+ const dir = credentialHome(config, env);
11489
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
11490
+ fs.chmodSync(dir, 448);
11491
+ const path = authFilePath(config, env);
11492
+ const tmp = `${path}.tmp-${crypto.randomBytes(8).toString("hex")}`;
11493
+ try {
11494
+ const fd = fs.openSync(tmp, "wx", 384);
11495
+ try {
11496
+ fs.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}
11497
+ `);
11498
+ fs.fsyncSync(fd);
11499
+ } finally {
11500
+ fs.closeSync(fd);
11501
+ }
11502
+ fs.chmodSync(tmp, 384);
11503
+ fs.renameSync(tmp, path);
11504
+ } catch (err) {
11505
+ try {
11506
+ fs.unlinkSync(tmp);
11507
+ } catch {
11508
+ }
11509
+ throw new CredentialError(`cannot write ${path}: ${err.message}`);
11510
+ }
11511
+ return path;
11512
+ }
11513
+
11514
+ // src/server/auth/errors.ts
11515
+ var AuthCallbackError = class extends Error {
11516
+ name = "AuthCallbackError";
11517
+ code;
11518
+ constructor(code, message) {
11519
+ super(message ?? `OAuth callback error: ${code}`);
11520
+ this.code = code;
11521
+ }
11522
+ };
11523
+
11524
+ // src/internal/auth/oauth-engine.ts
11525
+ var REFRESH_SKEW_MS = 6e4;
11526
+ function parseTokenResponse(body, now) {
11527
+ const b = body;
11528
+ if (typeof b.access_token !== "string" || b.access_token.length === 0) {
11529
+ throw new AuthCallbackError(
11530
+ "oauth_token_exchange_failed",
11531
+ "token response had no access_token"
11532
+ );
11533
+ }
11534
+ if (typeof b.refresh_token !== "string" || b.refresh_token.length === 0) {
11535
+ throw new AuthCallbackError(
11536
+ "oauth_token_exchange_failed",
11537
+ "token response had no refresh_token"
11538
+ );
11539
+ }
11540
+ const expiresIn = typeof b.expires_in === "number" ? b.expires_in : 3600;
11541
+ return {
11542
+ access: b.access_token,
11543
+ refresh: b.refresh_token,
11544
+ expires: now + expiresIn * 1e3,
11545
+ ...typeof b.account_id === "string" ? { accountId: b.account_id } : {}
11546
+ };
11547
+ }
11548
+ async function postGrant(config, form, deps) {
11549
+ let res;
11550
+ try {
11551
+ res = await deps.fetch(config.tokenEndpoint, {
11552
+ method: "POST",
11553
+ headers: { "content-type": "application/x-www-form-urlencoded", accept: "application/json" },
11554
+ body: new URLSearchParams(form).toString()
11555
+ });
11556
+ } catch (err) {
11557
+ throw new AuthCallbackError(
11558
+ "oauth_token_exchange_failed",
11559
+ `token endpoint request failed: ${err.message}`
11560
+ );
11561
+ }
11562
+ if (!res.ok) {
11563
+ throw new AuthCallbackError(
11564
+ "oauth_token_exchange_failed",
11565
+ `token endpoint returned HTTP ${res.status}`
11566
+ );
11567
+ }
11568
+ let json;
11569
+ try {
11570
+ json = await res.json();
11571
+ } catch {
11572
+ throw new AuthCallbackError("oauth_token_exchange_failed", "token response was not valid JSON");
11573
+ }
11574
+ return parseTokenResponse(json, deps.now());
11575
+ }
11576
+ function refreshOAuthTokens(config, refresh, deps) {
11577
+ return postGrant(
11578
+ config,
11579
+ { grant_type: "refresh_token", refresh_token: refresh, client_id: config.clientId },
11580
+ deps
11581
+ );
11582
+ }
11583
+ function persistOAuthTokens(provider, tokens, store, env = {}) {
11584
+ return writeCredential(
11585
+ {
11586
+ type: "oauth",
11587
+ provider,
11588
+ access: tokens.access,
11589
+ refresh: tokens.refresh,
11590
+ expires: tokens.expires,
11591
+ ...tokens.accountId !== void 0 ? { account_id: tokens.accountId } : {}
11592
+ },
11593
+ store,
11594
+ env
11595
+ );
11596
+ }
11597
+ var inFlightRefresh = /* @__PURE__ */ new Map();
11598
+ async function ensureFreshCredential(resolved, opts, deps) {
11599
+ if (resolved.kind !== "oauth") return resolved;
11600
+ const now = deps.now();
11601
+ if (resolved.expiresAt !== void 0 && resolved.expiresAt > now + REFRESH_SKEW_MS) {
11602
+ return resolved;
11603
+ }
11604
+ const env = opts.env ?? {};
11605
+ const path = authFilePath(opts.store, env);
11606
+ let refresh = inFlightRefresh.get(path);
11607
+ if (refresh === void 0) {
11608
+ refresh = (async () => {
11609
+ const stored = readStoredOAuth(opts.store, env);
11610
+ if (stored === void 0) {
11611
+ throw new AuthCallbackError(
11612
+ "oauth_token_exchange_failed",
11613
+ "no stored oauth credential to refresh"
11614
+ );
11615
+ }
11616
+ const fresh2 = await refreshOAuthTokens(opts.config, stored.refresh, deps);
11617
+ const merged = {
11618
+ ...fresh2,
11619
+ accountId: fresh2.accountId ?? stored.account_id
11620
+ };
11621
+ persistOAuthTokens(resolved.provider, merged, opts.store, env);
11622
+ return merged;
11623
+ })();
11624
+ inFlightRefresh.set(path, refresh);
11625
+ refresh.finally(() => inFlightRefresh.delete(path)).catch(() => {
11626
+ });
11627
+ }
11628
+ const fresh = await refresh;
11629
+ return {
11630
+ kind: "oauth",
11631
+ provider: resolved.provider,
11632
+ apiKey: fresh.access,
11633
+ source: resolved.source,
11634
+ inferred: false,
11635
+ expiresAt: fresh.expires
11636
+ };
11637
+ }
11638
+
11639
+ // src/internal/auth/resolve-credential.ts
11640
+ async function resolveOAuth(stored, path, opts, env) {
11641
+ if (stored.provider !== opts.provider) return void 0;
11642
+ const base = {
11643
+ kind: "oauth",
11644
+ provider: opts.provider,
11645
+ apiKey: stored.access,
11646
+ source: path,
11647
+ inferred: false,
11648
+ expiresAt: stored.expires
11649
+ };
11650
+ if (opts.oauth === void 0) return base;
11651
+ const deps = {
11652
+ fetch: opts.deps?.fetch ?? fetch,
11653
+ now: opts.deps?.now ?? (() => Date.now())
11654
+ };
11655
+ return ensureFreshCredential(base, { config: opts.oauth, store: opts.store, env }, deps);
11656
+ }
11657
+ async function resolveCredential(opts) {
11658
+ const env = opts.env ?? {};
11659
+ const stored = readAuthFile(opts.store, env);
11660
+ if (stored === void 0) return void 0;
11661
+ const path = authFilePath(opts.store, env);
11662
+ if (stored.type === "oauth") {
11663
+ return resolveOAuth(stored, path, opts, env);
11664
+ }
11665
+ if (stored.api_key.length === 0) return void 0;
11666
+ if (stored.provider !== opts.provider) return void 0;
11667
+ return {
11668
+ kind: "api",
11669
+ provider: opts.provider,
11670
+ apiKey: stored.api_key,
11671
+ source: path,
11672
+ inferred: false
11673
+ };
11674
+ }
11675
+
11676
+ // src/internal/providers/builtin/openai-chatgpt.ts
11677
+ var DEFAULT_STORE = {
11678
+ home: os.homedir(),
11679
+ dirName: ".theokit",
11680
+ fileName: "auth.json",
11681
+ homeEnvVar: "THEOKIT_HOME"
11682
+ };
11683
+ var OPENAI_OAUTH_CONFIG = {
11684
+ provider: "openai",
11685
+ authorizeEndpoint: "https://auth.openai.com/oauth/authorize",
11686
+ tokenEndpoint: "https://auth.openai.com/oauth/token",
11687
+ clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
11688
+ scopes: ["openid", "profile", "email", "offline_access"],
11689
+ redirectUri: "https://auth.openai.com/deviceauth/callback"
11690
+ };
11691
+ function codexFetch() {
11692
+ return (async (input, init) => {
11693
+ const env = process.env;
11694
+ const resolved = await resolveCredential({
11695
+ provider: "openai",
11696
+ store: DEFAULT_STORE,
11697
+ oauth: OPENAI_OAUTH_CONFIG,
11698
+ env
11699
+ });
11700
+ if (resolved === void 0) {
11701
+ throw new Error(
11702
+ 'openai-chatgpt: no ChatGPT credential found \u2014 run the OpenAI device login (e.g. "/login openai") first.'
11703
+ );
11704
+ }
11705
+ const accountId = readStoredOAuth(DEFAULT_STORE, env)?.account_id;
11706
+ const headers = new Headers(init?.headers);
11707
+ headers.set("authorization", `Bearer ${resolved.apiKey}`);
11708
+ if (accountId !== void 0) headers.set("ChatGPT-Account-Id", accountId);
11709
+ return fetch(input, { ...init, headers });
11710
+ });
11711
+ }
11712
+ var OPENAI_CHATGPT = {
11713
+ name: "openai-chatgpt",
11714
+ apiMode: "responses_api",
11715
+ authType: "oauth_device_code",
11716
+ baseUrl: "https://chatgpt.com/backend-api/codex",
11717
+ envVars: [],
11718
+ fallbackModels: [
11719
+ "openai-chatgpt/gpt-5.4",
11720
+ "openai-chatgpt/gpt-5.4-mini",
11721
+ "openai-chatgpt/gpt-5.5"
11722
+ ],
11723
+ extraHeaders: { originator: "codex_cli_rs" },
11724
+ transform: {
11725
+ // Only `fetch` (async) can await the credential refresh; `headers` is sync and cannot.
11726
+ fetch: () => codexFetch()
11727
+ }
11728
+ };
11377
11729
 
11378
11730
  // src/internal/providers/builtin/openrouter.ts
11379
11731
  var OPENROUTER = {
@@ -11427,6 +11779,7 @@ function registerBuiltins() {
11427
11779
  registered2 = true;
11428
11780
  registerProvider(ANTHROPIC);
11429
11781
  registerProvider(OPENAI);
11782
+ registerProvider(OPENAI_CHATGPT);
11430
11783
  registerProvider(OPENROUTER);
11431
11784
  registerProvider(GEMINI);
11432
11785
  registerProvider(OLLAMA);
@@ -13689,6 +14042,15 @@ function selectTransport(profile, apiKey) {
13689
14042
  const ctx = { apiKey };
13690
14043
  return { fetch: profile.transform.fetch?.(ctx), headers: profile.transform.headers?.(ctx) };
13691
14044
  };
14045
+ const assertOAuthResolved = (t) => {
14046
+ if (apiKey !== "__oauth_lazy_token__") return;
14047
+ const auth = t.headers?.authorization ?? t.headers?.Authorization;
14048
+ if (t.fetch === void 0 && auth === void 0) {
14049
+ throw new ConfigurationError(
14050
+ `provider "${profile.name}" uses OAuth (authType: ${profile.authType}) but no credential was resolved. Register a ProviderProfile.transform whose fetch (or headers.authorization) supplies the bearer \u2014 typically via resolveCredential() from "@theokit/sdk/auth".`
14051
+ );
14052
+ }
14053
+ };
13692
14054
  if (profile.apiMode === "chat_completions") {
13693
14055
  if (profile.name === "ollama") {
13694
14056
  const ollamaBase = process.env.OLLAMA_HOST ?? profile.baseUrl;
@@ -13706,6 +14068,7 @@ function selectTransport(profile, apiKey) {
13706
14068
  const envOverride = resolveBaseUrlEnvOverride(profile.name);
13707
14069
  if (envOverride !== void 0) opts.baseUrl = envOverride;
13708
14070
  const t = applyTransform();
14071
+ assertOAuthResolved(t);
13709
14072
  if (t.fetch !== void 0) opts.fetch = t.fetch;
13710
14073
  const merged = profile.extraHeaders !== void 0 || t.headers !== void 0 ? { ...profile.extraHeaders, ...t.headers } : void 0;
13711
14074
  if (merged !== void 0) opts.extraHeaders = merged;
@@ -13726,6 +14089,7 @@ function selectTransport(profile, apiKey) {
13726
14089
  }
13727
14090
  if (profile.apiMode === "responses_api") {
13728
14091
  const t = applyTransform();
14092
+ assertOAuthResolved(t);
13729
14093
  const mergedHeaders = profile.extraHeaders !== void 0 || t.headers !== void 0 ? { ...profile.extraHeaders, ...t.headers } : void 0;
13730
14094
  return new ResponsesApiClient({
13731
14095
  apiKey,