workspai 0.78.0 → 0.79.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.
Files changed (75) hide show
  1. package/README.md +117 -236
  2. package/contracts/create-planner-capabilities.v1.json +1 -1
  3. package/dist/analyze-SKOIUD6F.js +1 -0
  4. package/dist/autopilot-release-E7ADQH2B.js +1 -0
  5. package/dist/{chunk-5FVREPQC.js → chunk-2ZVE7VE2.js} +1 -1
  6. package/dist/{chunk-4KAYWMNE.js → chunk-3O344BX3.js} +1 -1
  7. package/dist/{chunk-HMWZWT7B.js → chunk-5MRDPZU7.js} +1 -1
  8. package/dist/{chunk-RJESUJQF.js → chunk-5NS322HT.js} +28 -28
  9. package/dist/{chunk-B6FJ6RG6.js → chunk-6CZQXEZ7.js} +1 -1
  10. package/dist/{chunk-75WJOYWT.js → chunk-CAGOLU4Q.js} +1 -1
  11. package/dist/{chunk-2CUT4GLE.js → chunk-DYSFGHV5.js} +1 -1
  12. package/dist/{chunk-4AAXIWYY.js → chunk-ETTZXKAS.js} +1 -1
  13. package/dist/{chunk-FLAGHVSH.js → chunk-FJMUBFXO.js} +1 -1
  14. package/dist/{chunk-RHF2IZOW.js → chunk-GIP7YFWR.js} +1 -1
  15. package/dist/{chunk-YWC5MKIL.js → chunk-IF2OWXNY.js} +1 -1
  16. package/dist/{chunk-UVIBPWOG.js → chunk-L3O2QXKH.js} +2 -2
  17. package/dist/{chunk-ONUFNCRN.js → chunk-LQYMYRGI.js} +1 -1
  18. package/dist/{chunk-YHQK4S3H.js → chunk-MJYWGRCY.js} +1 -1
  19. package/dist/{chunk-MN5S462C.js → chunk-MNZAJ6PP.js} +1 -1
  20. package/dist/{chunk-VRZAN3WS.js → chunk-NZQKHGPE.js} +1 -1
  21. package/dist/{chunk-QDM6TWFK.js → chunk-O4TZKLEC.js} +1 -1
  22. package/dist/{chunk-5OKNWBAN.js → chunk-R7R5FX4B.js} +1 -1
  23. package/dist/{chunk-HB3ZYG2Z.js → chunk-RRZQPFFR.js} +69 -60
  24. package/dist/{chunk-VEMGICT4.js → chunk-SKHRUHXO.js} +1 -1
  25. package/dist/{chunk-AMGIXK6Z.js → chunk-TQW2SZHH.js} +1 -1
  26. package/dist/{chunk-TJRVWVAZ.js → chunk-U5WP4HDH.js} +1 -1
  27. package/dist/{chunk-QMNJTI65.js → chunk-VT4WPXTO.js} +1 -1
  28. package/dist/chunk-WJKQTX4V.js +2 -0
  29. package/dist/{chunk-MD4CHXV3.js → chunk-WU5M6UP7.js} +1 -1
  30. package/dist/{chunk-IQJL3LBM.js → chunk-ZONAHZBG.js} +1 -1
  31. package/dist/{chunk-ZQ36CTLF.js → chunk-ZXYTWF2R.js} +1 -1
  32. package/dist/{create-WB5YQ7C6.js → create-2ETTECFU.js} +1 -1
  33. package/dist/{doctor-HOSHQ7II.js → doctor-3F7OUVYQ.js} +1 -1
  34. package/dist/{goal-interactive-selection-6YJOK4WC.js → goal-interactive-selection-EMHAM5TM.js} +1 -1
  35. package/dist/{goal-lifecycle-UEJMTUFV.js → goal-lifecycle-PR5O7T6B.js} +1 -1
  36. package/dist/{goal-pack-YGZMG3RQ.js → goal-pack-TEK2PHAV.js} +1 -1
  37. package/dist/index.js +159 -159
  38. package/dist/{pipeline-Z4JOJ4PX.js → pipeline-E6YY65GU.js} +1 -1
  39. package/dist/{project-agent-entry-ZMDQWJ6I.js → project-agent-entry-MXAHZSQR.js} +1 -1
  40. package/dist/{project-intelligence-lens-FSWQKY3E.js → project-intelligence-lens-IUM66OV2.js} +1 -1
  41. package/dist/{proof-carrying-change-M2RNYQ7E.js → proof-carrying-change-XA5HRGSB.js} +1 -1
  42. package/dist/{verified-goal-UAUCEDZZ.js → verified-goal-V5OVULY7.js} +1 -1
  43. package/dist/{workspace-J5QYWAE3.js → workspace-XVPKQG5W.js} +1 -1
  44. package/dist/{workspace-agent-sync-GLG36KQZ.js → workspace-agent-sync-46L7ZOQQ.js} +1 -1
  45. package/dist/{workspace-context-BZV4ZFKZ.js → workspace-context-VVZARI5K.js} +1 -1
  46. package/dist/{workspace-contract-XDNIB5JX.js → workspace-contract-N2BJCAJ7.js} +1 -1
  47. package/dist/{workspace-explain-V3XDMONQ.js → workspace-explain-5WKK6ENK.js} +1 -1
  48. package/dist/{workspace-foundation-EDTKW2RB.js → workspace-foundation-PZNZWUJ4.js} +1 -1
  49. package/dist/{workspace-graph-stream-UPASFCAF.js → workspace-graph-stream-26ME2JJS.js} +1 -1
  50. package/dist/{workspace-intelligence-24MZRG2S.js → workspace-intelligence-ZTET7JHS.js} +1 -1
  51. package/dist/{workspace-intelligence-runner-HISKHQLG.js → workspace-intelligence-runner-NZFRFCPN.js} +1 -1
  52. package/dist/{workspace-knowledge-graph-5EECTGXQ.js → workspace-knowledge-graph-4RSZTEFJ.js} +1 -1
  53. package/dist/workspace-knowledge-graph-snapshot-UTJHALST.js +1 -0
  54. package/dist/{workspace-mcp-serve-S3TWO4RI.js → workspace-mcp-serve-TIYCW364.js} +1 -1
  55. package/dist/{workspace-model-J757KSDE.js → workspace-model-LBICKRVI.js} +1 -1
  56. package/dist/{workspace-onboarding-3TLME4HP.js → workspace-onboarding-WUO6KCLF.js} +1 -1
  57. package/dist/{workspace-registry-summary-ZC5CFZXP.js → workspace-registry-summary-3PXZENLC.js} +1 -1
  58. package/dist/{workspace-repair-engine-C26C7OIB.js → workspace-repair-engine-V4Z7TJGS.js} +1 -1
  59. package/dist/workspace-run-T65CCZIN.js +1 -0
  60. package/dist/{workspace-verify-KFBWQ37C.js → workspace-verify-UQWAFGNQ.js} +1 -1
  61. package/dist/{workspace-watch-LNP5JPD4.js → workspace-watch-BKIZQ2SM.js} +1 -1
  62. package/docs/README.md +48 -116
  63. package/docs/agent-framework-adapters.md +32 -26
  64. package/docs/ci-workflows.md +8 -2
  65. package/docs/commands-reference.md +13 -12
  66. package/docs/create-planner-capabilities.md +1 -1
  67. package/docs/creating-workspaces-and-projects.md +29 -22
  68. package/docs/model-gateways.md +23 -23
  69. package/package.json +2 -2
  70. package/dist/analyze-X76YV667.js +0 -1
  71. package/dist/autopilot-release-6OV2FLRS.js +0 -1
  72. package/dist/chunk-3PGXGAHC.js +0 -2
  73. package/dist/workspace-knowledge-graph-snapshot-T6ZJGWLC.js +0 -1
  74. package/dist/workspace-run-2KBTMGG7.js +0 -1
  75. /package/dist/{chunk-QLHRGWUW.js → chunk-GOOY7I36.js} +0 -0
@@ -1,4 +1,4 @@
1
- import {a as a$1}from'./chunk-MD4CHXV3.js';import {j}from'./chunk-7HV4U2OV.js';import {m}from'./chunk-7EMG3C3H.js';import {a as a$2}from'./chunk-AFIFZTOY.js';import {i,e,h as h$3,c}from'./chunk-6BO4QHKA.js';import {e as e$1}from'./chunk-HV6EQMUJ.js';import {h as h$2}from'./chunk-EFHSRPW7.js';import {b as b$1,a}from'./chunk-TQEMLBAG.js';import {h as h$1,v,t,k as k$1,u}from'./chunk-VQZ5PI5J.js';import {p,s}from'./chunk-ODK4LCEL.js';import go from'validate-npm-package-name';import x from'path';import {spawn}from'child_process';import E from'chalk';import M from'fs-extra';import {createHash}from'crypto';import ge from'fs/promises';import xa from'fs';function mo(e,t){return t==="win32"?`Install Python ${e}+ with Windows Package Manager:
1
+ import {a as a$1}from'./chunk-WU5M6UP7.js';import {j}from'./chunk-7HV4U2OV.js';import {m}from'./chunk-7EMG3C3H.js';import {a as a$2}from'./chunk-AFIFZTOY.js';import {i,e,h as h$3,c}from'./chunk-6BO4QHKA.js';import {e as e$1}from'./chunk-HV6EQMUJ.js';import {h as h$2}from'./chunk-EFHSRPW7.js';import {b as b$1,a}from'./chunk-TQEMLBAG.js';import {h as h$1,v,t,k as k$1,u}from'./chunk-VQZ5PI5J.js';import {p,s}from'./chunk-ODK4LCEL.js';import go from'validate-npm-package-name';import x from'path';import {spawn}from'child_process';import E from'chalk';import M from'fs-extra';import {createHash}from'crypto';import ge from'fs/promises';import xa from'fs';function mo(e,t){return t==="win32"?`Install Python ${e}+ with Windows Package Manager:
2
2
  winget install --exact --id Python.Python.${e}
3
3
 
4
4
  Or use the official installer and enable "Add Python to PATH":
@@ -76,7 +76,7 @@ Then verify:
76
76
  Alpine: sudo apk add python3 py3-pip py3-virtualenv
77
77
 
78
78
  Then verify:
79
- python3 -m venv --help`}var N=class extends Error{constructor(r,o,a){super(r);this.code=o;this.details=a;this.name="RapidKitError",Error.captureStackTrace(this,this.constructor);}code;details},Yt=class extends N{constructor(t,r,o=process.platform){let a=r?`Python ${t}+ required, found ${r}`:`Python ${t}+ not found`;super(a,"PYTHON_NOT_FOUND",mo(t,o));}},Xt=class extends N{constructor(t=process.platform){super("Poetry is not installed","POETRY_NOT_FOUND",uo(t));}},Ht=class extends N{constructor(t=process.platform){super("pipx is not installed","PIPX_NOT_FOUND",rr(t));}},Jt=class extends N{constructor(t){super(`Directory "${t}" already exists`,"DIRECTORY_EXISTS","Please choose a different name or remove the existing directory");}},D=class extends N{constructor(t,r){super(`Invalid project name: "${t}"`,"INVALID_PROJECT_NAME",r);}},Zt=class extends N{constructor(t,r){let o=`Installation failed at: ${t}`,a=`${r.message}
79
+ python3 -m venv --help`}var N=class extends Error{constructor(r,o,a){super(r);this.code=o;this.details=a;this.name="RapidKitError",Error.captureStackTrace(this,this.constructor);}code;details},Yt=class extends N{constructor(t,r,o=process.platform){let a=r?`Python ${t}+ required, found ${r}`:`Python ${t}+ not found`;super(a,"PYTHON_NOT_FOUND",mo(t,o));}},Xt=class extends N{constructor(t=process.platform){super("Poetry is not installed","POETRY_NOT_FOUND",uo(t));}},Jt=class extends N{constructor(t=process.platform){super("pipx is not installed","PIPX_NOT_FOUND",rr(t));}},Ht=class extends N{constructor(t){super(`Directory "${t}" already exists`,"DIRECTORY_EXISTS","Please choose a different name or remove the existing directory");}},D=class extends N{constructor(t,r){super(`Invalid project name: "${t}"`,"INVALID_PROJECT_NAME",r);}},Zt=class extends N{constructor(t,r){let o=`Installation failed at: ${t}`,a=`${r.message}
80
80
 
81
81
  Troubleshooting:
82
82
  - Check your internet connection
@@ -98,7 +98,7 @@ Alternatively, restore pip and retry:
98
98
  3. Offline fallback (limited): npx workspai create project fastapi.standard <name> --output .
99
99
 
100
100
  Legacy: set RAPIDKIT_SHOW_LEGACY=1 to reveal template-mode flags in help.`);}};function Pe(e){let t=go(e);if(!t.validForNewPackages){let o=t.errors||[],a=t.warnings||[],s=[...o,...a];throw new D(e,`NPM validation failed: ${s.join(", ")}`)}if(!/^[a-z][a-z0-9_-]*$/.test(e))throw new D(e,"Must start with a lowercase letter and contain only lowercase letters, numbers, hyphens, and underscores");if(["test","tests","src","dist","build","lib","python","pip","poetry","node","npm","rapidkit","rapidkit"].includes(e.toLowerCase()))throw new D(e,`"${e}" is a reserved name. Please choose a different name.`);if(e.length<2)throw new D(e,"Name must be at least 2 characters long");if(e.length>214)throw new D(e,"Name must be less than 214 characters");return true}async function Ne(e,t){let r={},o={};return await _o(e,r,o),await yo(e,r,o),{policy:"latest-stable",requested_channel:t,host:{node:process.versions.node,platform:process.platform,arch:process.arch},declared:or(r),resolved:or(o)}}async function _o(e,t,r){let o=x.join(e,"package.json");if(!await M.pathExists(o))return;let a=await M.readJson(o);for(let c of ["dependencies","devDependencies","peerDependencies"]){let i=gt(a[c]);for(let[p,_]of Object.entries(i))t[`npm:${p}`]=_;}for(let[c,i]of Object.entries(gt(a.engines)))t[`engine:${c}`]=i;let s=x.join(e,"package-lock.json");if(!await M.pathExists(s))return;let n=await M.readJson(s),l=_t(n.packages);for(let c of Object.keys(t).filter(i=>i.startsWith("npm:")).map(i=>i.slice(4))){let i=_t(l[`node_modules/${c}`]);typeof i.version=="string"&&(r[`npm:${c}`]=i.version);}}async function yo(e,t,r){let o=x.join(e,"composer.json");if(!await M.pathExists(o))return;let a=await M.readJson(o);for(let l of ["require","require-dev"])for(let[c,i]of Object.entries(gt(a[l])))t[`composer:${c}`]=i;let s=x.join(e,"composer.lock");if(!await M.pathExists(s))return;let n=await M.readJson(s);for(let l of ["packages","packages-dev"]){let c=Array.isArray(n[l])?n[l]:[];for(let i of c){let p=_t(i);typeof p.name=="string"&&typeof p.version=="string"&&(r[`composer:${p.name}`]=p.version);}}}function gt(e){return !e||typeof e!="object"||Array.isArray(e)?{}:Object.fromEntries(Object.entries(e).filter(t=>typeof t[1]=="string"))}function _t(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:{}}function or(e){return Object.fromEntries(Object.entries(e).sort(([t],[r])=>t.localeCompare(r)))}var nr=[{id:"nextjs",kitId:"frontend.nextjs",aliases:["frontend.nextjs","next","nextjs","next.js"],displayName:"Next.js",framework:"nextjs",defaultPort:3e3,versionPolicy:"latest-stable",commandDisplay:e=>`npx create-next-app@latest ${e}`,commandExec:(e,t)=>({command:"npx",args:["--yes","create-next-app@latest",e,"--yes",...t.skipGit?["--disable-git"]:[],...t.skipInstall?["--skip-install"]:[]]})},{id:"remix",kitId:"frontend.remix",aliases:["frontend.remix","remix","remix-run","react-router"],displayName:"React Router",framework:"remix",defaultPort:5173,versionPolicy:"latest-stable",minNodeVersion:"22.22.0",minNodeMessage:"The latest stable React Router generator requires Node.js 22.22.0 or newer. Upgrade Node.js, then retry.",commandDisplay:e=>`npx create-react-router@latest ${e}`,commandExec:(e,t)=>({command:"npx",args:["--yes","create-react-router@latest",e,"--yes",...t.skipInstall?["--no-install"]:["--install"],"--no-git-init"]})},{id:"vite-react",kitId:"frontend.vite-react",aliases:["frontend.vite-react","vite-react","react","vite.react"],displayName:"React + Vite",framework:"react",defaultPort:5173,versionPolicy:"latest-stable",commandDisplay:e=>`npm create vite@latest ${e} -- --template react-ts`,commandExec:e=>({command:"npm",args:["create","vite@latest",e,"--","--template","react-ts","--no-interactive"]})},{id:"vite-vue",kitId:"frontend.vite-vue",aliases:["frontend.vite-vue","vite-vue","vue","vite.vue"],displayName:"Vue + Vite",framework:"vue",defaultPort:5173,versionPolicy:"latest-stable",commandDisplay:e=>`npm create vite@latest ${e} -- --template vue-ts`,commandExec:e=>({command:"npm",args:["create","vite@latest",e,"--","--template","vue-ts","--no-interactive"]})},{id:"vite-svelte",kitId:"frontend.vite-svelte",aliases:["frontend.vite-svelte","vite-svelte","svelte","vite.svelte"],displayName:"Svelte + Vite",framework:"svelte",defaultPort:5173,versionPolicy:"latest-stable",commandDisplay:e=>`npm create vite@latest ${e} -- --template svelte-ts`,commandExec:e=>({command:"npm",args:["create","vite@latest",e,"--","--template","svelte-ts","--no-interactive"]})},{id:"vite-solid",kitId:"frontend.vite-solid",aliases:["frontend.vite-solid","vite-solid","solid","solidjs","vite.solid"],displayName:"Solid + Vite",framework:"solid",defaultPort:5173,versionPolicy:"latest-stable",commandDisplay:e=>`npm create vite@latest ${e} -- --template solid-ts`,commandExec:e=>({command:"npm",args:["create","vite@latest",e,"--","--template","solid-ts","--no-interactive"]})},{id:"vite-vanilla",kitId:"frontend.vite-vanilla",aliases:["frontend.vite-vanilla","vite","vanilla","vite-vanilla"],displayName:"Vite",framework:"vite",defaultPort:5173,versionPolicy:"latest-stable",commandDisplay:e=>`npm create vite@latest ${e} -- --template vanilla-ts`,commandExec:e=>({command:"npm",args:["create","vite@latest",e,"--","--template","vanilla-ts","--no-interactive"]})},{id:"nuxt",kitId:"frontend.nuxt",aliases:["frontend.nuxt","nuxt","nuxtjs","nuxt.js"],displayName:"Nuxt",framework:"nuxt",defaultPort:3e3,versionPolicy:"latest-stable",commandDisplay:(e,t)=>`npx create-nuxt@latest ${e} --template minimal --packageManager npm --gitInit ${t?.skipGit?"false":"true"}${t?.skipInstall?" --no-install":""}`,commandExec:(e,t)=>({command:"npx",args:["--yes","create-nuxt@latest",e,"--template","minimal","--packageManager","npm","--gitInit",t.skipGit?"false":"true",...t.skipInstall?["--no-install"]:[]]})},{id:"angular",kitId:"frontend.angular",aliases:["frontend.angular","angular","ng"],displayName:"Angular",framework:"angular",defaultPort:4200,versionPolicy:"latest-stable",minNodeMajor:20,minNodeMessage:"The latest stable Angular CLI requires a modern Node.js runtime. Upgrade to Node.js 20.19+ (or a newer supported LTS), then retry.",commandDisplay:e=>`npx @angular/cli@latest new ${e}`,commandExec:(e,t)=>({command:"npx",args:["--yes","@angular/cli@latest","new",e,"--defaults","--skip-git",...t.skipInstall?["--skip-install"]:[]]})},{id:"astro",kitId:"frontend.astro",aliases:["frontend.astro","astro"],displayName:"Astro",framework:"astro",defaultPort:4321,versionPolicy:"latest-stable",commandDisplay:e=>`npm create astro@latest ${e}`,commandExec:(e,t)=>({command:"npm",args:["create","astro@latest",e,"--","--yes",...t.skipInstall?["--no-install"]:[],...t.skipGit?["--no-git"]:[]]})},{id:"sveltekit",kitId:"frontend.sveltekit",aliases:["frontend.sveltekit","sveltekit","svelte-kit"],displayName:"SvelteKit",framework:"sveltekit",defaultPort:5173,versionPolicy:"latest-stable",commandDisplay:e=>`npx sv@latest create ${e}`,commandExec:(e,t)=>({command:"npx",args:["--yes","sv@latest","create",e,"--template","minimal","--types","ts","--no-add-ons",...t.skipInstall?["--no-install"]:["--install","npm"]]})}],Ie=new Map;for(let e of nr){Ie.set(e.id,e),Ie.set(e.kitId,e);for(let t of e.aliases)Ie.set(t.toLowerCase(),e);}function Za(){return [...nr]}function fe(e){return e?Ie.get(e.trim().toLowerCase())??null:null}function Qa(e){return !!fe(e)}function es(e){if(e[0]!=="create"||e[1]!=="frontend")return null;let t=e[2],r=e[3],o=e.slice(4),a=fe(t);return a?["create","project",a.kitId,r??"",...o].filter(Boolean):["create","project",`frontend.${t??""}`,r??"",...o].filter(Boolean)}function ar(e,t){return `workspai create project ${fe(e)?.id??e.replace(/^frontend\./,"")} ${t}`}function sr(e,t){return `npx ${ar(e,t)}`}function wo(e){let t=fe(e);return t?`${ar(t.id,"<name>")} [--output <dir>] [--skip-install] [--dry-run]`:"workspai create project <nextjs|remix|vite-react|vite-vue|vite-svelte|vite-solid|vite-vanilla|nuxt|angular|astro|sveltekit> <name> [--output <dir>] [--skip-install] [--dry-run]"}async function ts(e){let t=e.args;if(t[0]!=="create"||t[1]!=="project")throw new Error("Frontend create expects normalized args: create project <frontend.kit> <name>");let r=fe(t[2]);if(!r)throw new Error(`Unknown frontend generator: ${t[2]??"(missing)"}`);let o=t[3];if(!o)throw new Error(`Usage: ${wo(r.id)}`);Pe(o);let a=To(t,"--output")||process.cwd(),s=x.resolve(a,o),n=e.dryRun===true||t.includes("--dry-run"),l=t.includes("--skip-install"),c=t.includes("--skip-git")||t.includes("--no-git"),i=r.commandExec(o,{skipGit:c,skipInstall:l}),p=r.commandDisplay(o,{skipGit:c,skipInstall:l});if(await M.pathExists(s))throw new Error(`Directory "${s}" already exists`);if(n)return Eo({definition:r,projectName:o,projectPath:s,commandPlan:i}),{definition:r,projectName:o,projectPath:s,dryRun:n,commandDisplay:p,commandExec:[i.command,...i.args]};Ao(r),await M.ensureDir(x.dirname(s));try{let _=await ir(i.command,i.args,x.dirname(s)),I=await Ro(s);if(_!==0)throw new Error(`Official ${r.displayName} generator failed with exit code ${_}`);if(!I)throw new Error(`Official ${r.displayName} generator did not produce a valid package.json`);c||await bo(s),await ko({definition:r,projectName:o,projectPath:s,commandDisplay:p,commandExec:[i.command,...i.args],skipGit:c,skipInstall:l});}catch(_){throw await M.remove(s),_}return console.log(E.green(`\u2705 ${r.displayName} project created at ${s}`)),console.log(E.gray(` Display command: ${sr(r.id,o)}`)),console.log(E.gray(" Next: cd "+o+" && npx workspai dev")),{definition:r,projectName:o,projectPath:s,dryRun:n,commandDisplay:p,commandExec:[i.command,...i.args]}}async function ko(e){let t=new Date().toISOString(),r=await h$2(),o=await Ne(e.projectPath,"latest"),a={schema_version:"1.0",name:e.projectName,slug:e.projectName,kind:"frontend",project_type:"frontend",category:"frontend",runtime:"node",framework:e.definition.framework,framework_display_name:e.definition.displayName,kit_name:e.definition.kitId,kit:e.definition.kitId,engine:"npm",support_tier:"extended",module_support:false,modules:[],workspai_version:r,rapidkit_version:r,generated_by:"workspai",generated_at:t,frontend:{generator:e.definition.id,official_generator:true,version_policy:e.definition.versionPolicy,version_evidence:o,default_port:e.definition.defaultPort,command_display:e.commandDisplay,command_exec:e.commandExec,skip_install:e.skipInstall,skip_git:e.skipGit},contracts:{owns:[],apis:[],publishes:[],consumes:[],dependsOn:[],env:[]}},s={project:e.projectName,runtime:"node",framework:e.definition.framework,kind:"frontend",category:"frontend",source:"official-generator"},n={kind:"workspai.frontend_create",schema_version:"1.0",generated_at:t,project:{name:e.projectName,path:e.projectPath,kind:"frontend",category:"frontend",runtime:"node",framework:e.definition.framework,framework_display_name:e.definition.displayName,kit_name:e.definition.kitId},generator:{id:e.definition.id,version_policy:e.definition.versionPolicy,version_evidence:o,command_display:e.commandDisplay,command_exec:e.commandExec}};for(let[l,c]of [["project.json",a],["context.json",s],["frontend-create.json",n]]){let i=v(e.projectPath,l);await M.ensureDir(x.dirname(i)),await M.writeJson(i,c,{spaces:2});}}function Eo(e){console.log(E.bold(`
101
- Workspai frontend create plan: ${e.definition.displayName}`)),console.log(E.gray(`Project: ${e.projectName}`)),console.log(E.gray(`Target: ${e.projectPath}`)),console.log(E.gray(`Show: ${sr(e.definition.id,e.projectName)}`)),console.log(E.gray(`Run: ${[e.commandPlan.command,...e.commandPlan.args].join(" ")}`)),console.log(E.gray(`Default: http://localhost:${e.definition.defaultPort}`));}function vo(){let e=Number.parseInt(process.versions.node.split(".")[0]??"",10);return Number.isFinite(e)?e:0}function Ao(e){if(!e.minNodeVersion&&!e.minNodeMajor||(e.minNodeVersion?xo(process.versions.node,e.minNodeVersion)>=0:vo()>=(e.minNodeMajor??0)))return;let r=e.minNodeVersion??`${e.minNodeMajor}+`;throw new Error(e.minNodeMessage??`${e.displayName} requires Node.js ${r} (current: ${process.versions.node}).`)}function xo(e,t){let r=e.split(".").map(s=>Number.parseInt(s,10)||0),o=t.split(".").map(s=>Number.parseInt(s,10)||0),a=Math.max(r.length,o.length);for(let s=0;s<a;s+=1){let n=(r[s]??0)-(o[s]??0);if(n!==0)return n}return 0}async function Ro(e){if(!await M.pathExists(e))return false;let t=x.join(e,"package.json");if(!await M.pathExists(t))return false;try{let r=await M.readJson(t);return r!==null&&typeof r=="object"&&!Array.isArray(r)}catch{return false}}async function bo(e){let t=x.join(e,".git");if(await M.pathExists(t))return;if(await b$1(e)){console.log(E.gray(" Git initialization skipped; target is inside an existing git worktree."));return}if(await ir("git",["init"],e)===0){console.log(E.gray(" Git repository initialized."));return}console.log(E.yellow("\u26A0\uFE0F Git initialization was skipped or failed. You can run `git init` manually inside the project."));}async function ir(e$1,t,r){let o=e(e$1);return await new Promise(a$1=>{let s=spawn(o.command,[...o.prefixArgs,...t],{cwd:r,stdio:"inherit",shell:false,env:e$1==="git"?a(h$3()):h$3()});s.on("close",n=>a$1(n??1)),s.on("error",()=>a$1(1));})}function To(e,t){let r=e.indexOf(t);if(r>=0&&r+1<e.length)return e[r+1];let o=e.find(a=>a.startsWith(`${t}=`));return o?o.slice(t.length+1):void 0}var dr=[{id:"tauri",kitId:"desktop.tauri",aliases:["desktop.tauri","tauri","tauri-app"],displayName:"Tauri",category:"desktop",kind:"desktop",runtime:"node",runtimeCandidates:["node","rust"],framework:"tauri",supportTier:"extended",versionPolicy:"latest-stable",defaultPort:1420,officialSource:"create-tauri-app",supportsSkipInstall:true,requiredTools:[{command:"rustc",args:["--version"],label:"Rust toolchain",guidance:"Install the current stable Rust toolchain from https://rustup.rs/ and retry."},{command:"cargo",args:["--version"],label:"Cargo",guidance:"Install Cargo through the current stable Rust toolchain and retry."}],commandDisplay:e=>`npm create tauri-app@latest ${e} -- --manager npm --template vanilla-ts --yes`,commandExec:e=>({command:"npm",args:["create","tauri-app@latest",e,"--","--manager","npm","--template","vanilla-ts","--yes"]}),requiredArtifacts:["package.json","src-tauri/Cargo.toml","src-tauri/tauri.conf.json"]},{id:"electron",kitId:"desktop.electron",aliases:["desktop.electron","electron","electron-forge"],displayName:"Electron Forge",category:"desktop",kind:"desktop",runtime:"node",runtimeCandidates:["node"],framework:"electron",supportTier:"extended",versionPolicy:"latest-stable",officialSource:"create-electron-app",supportsSkipInstall:false,requiredTools:[{command:"git",args:["--version"],label:"Git",guidance:"Install Git and make it available on PATH, then retry."}],commandDisplay:e=>`npx create-electron-app@latest ${e} --template=vite-typescript`,commandExec:e=>({command:"npx",args:["--yes","create-electron-app@latest",e,"--template=vite-typescript"]}),requiredArtifacts:["package.json"]},{id:"vscode-extension",kitId:"extension.vscode",aliases:["extension.vscode","vscode","vscode-extension","visual-studio-code-extension"],displayName:"VS Code Extension",category:"extension",kind:"extension",runtime:"node",runtimeCandidates:["node"],framework:"vscode-extension",supportTier:"extended",versionPolicy:"latest-stable",officialSource:"generator-code",supportsSkipInstall:false,nodeSupport:{requirement:"^22.22.2 || ^24.15.0 || >=26.0.0",ranges:[{major:22,minVersion:"22.22.2"},{major:24,minVersion:"24.15.0"},{minVersion:"26.0.0"}],guidance:"Install Node.js 22.22.2+, 24.15.0+, or 26+ before running the latest stable VS Code Extension generator."},requiredTools:[{command:"git",args:["--version"],label:"Git",guidance:"Install Git to initialize source control, or create the project with --skip-git."}],commandDisplay:e=>`npx --package yo@latest --package generator-code@latest -- yo code ${e} --quick --extensionType ts --pkgManager npm --bundle esbuild`,commandExec:(e,t)=>({command:"npx",args:["--yes","--package","yo@latest","--package","generator-code@latest","--","yo","code",e,"--quick","--extensionType","ts","--extensionId",e,"--pkgManager","npm","--bundle","esbuild",`--gitInit=${t.skipGit?"false":"true"}`]}),requiredArtifacts:["package.json","src/extension.ts"]},{id:"laravel",kitId:"php.laravel",aliases:["php.laravel","laravel","php-laravel"],displayName:"Laravel",category:"backend",kind:"backend",runtime:"php",runtimeCandidates:["php","node"],framework:"laravel",supportTier:"extended",versionPolicy:"latest-stable",defaultPort:8e3,officialSource:"composer-create-project",supportsSkipInstall:false,requiredTools:[{command:"php",args:["--version"],label:"PHP",guidance:"Install a PHP version supported by the latest stable Laravel release and retry."},{command:"composer",args:["--version"],label:"Composer",guidance:"Install the latest stable Composer release and retry."}],recommendedTools:[{command:"npm",args:["--version"],label:"npm",guidance:"Install a supported Node.js LTS runtime when you want to build Laravel frontend assets."}],commandDisplay:e=>`composer create-project --no-interaction --prefer-dist --stability=stable laravel/laravel ${e}`,commandExec:e=>({command:"composer",args:["create-project","--no-interaction","--prefer-dist","--stability=stable","laravel/laravel",e]}),requiredArtifacts:["composer.json","artisan"]}],Ce=new Map;for(let e of dr){Ce.set(e.id,e),Ce.set(e.kitId,e);for(let t of e.aliases)Ce.set(t.toLowerCase(),e);}function fs(){return [...dr]}function yt(e){return e?Ce.get(e.trim().toLowerCase())??null:null}function gs(e){return yt(e)!==null}function Oo(e){return `workspai create project ${yt(e)?.kitId??"<desktop.tauri|desktop.electron|extension.vscode|php.laravel>"} <name> [--output <dir>] [--skip-git] [--skip-install] [--dry-run]`}async function _s(e){if(e[0]!=="create"||e[1]!=="project")throw new Error("Official project create expects: create project <official.kit> <project-name>");let t=yt(e[2]);if(!t)throw new Error(`Unknown official project generator: ${e[2]??"(missing)"}`);let r=e[3];if(!r)throw new Error(`Usage: ${Oo(t.kitId)}`);Pe(r);let o=Go(e,"--output")||process.cwd(),a=x.resolve(o,r),s=e.includes("--dry-run"),n={skipGit:e.includes("--skip-git")||e.includes("--no-git"),skipInstall:e.includes("--skip-install")};if(n.skipInstall&&!t.supportsSkipInstall)throw new Error(`${t.displayName}'s official generator does not support --skip-install. Remove the flag so Workspai can verify the official scaffold.`);let l=t.commandExec(r,n),c=t.commandDisplay(r,n);if(await M.pathExists(a))throw new Error(`Directory "${a}" already exists`);if(s)return Mo(t,a,c,l),{definition:t,projectName:r,projectPath:a,dryRun:s,commandDisplay:c,commandExec:[l.command,...l.args]};Co(t),await Io(t,n),await M.ensureDir(x.dirname(a));try{let i=await No(l,x.dirname(a));if(i!==0)throw new Error(`Official ${t.displayName} generator failed with exit code ${i}`);await Po(t,a),n.skipGit&&await M.remove(x.join(a,".git")),await So({definition:t,projectName:r,projectPath:a,commandDisplay:c,commandExec:[l.command,...l.args],flags:n});}catch(i){throw await M.remove(a).catch(()=>{}),i}return console.log(E.green(`
101
+ Workspai frontend create plan: ${e.definition.displayName}`)),console.log(E.gray(`Project: ${e.projectName}`)),console.log(E.gray(`Target: ${e.projectPath}`)),console.log(E.gray(`Show: ${sr(e.definition.id,e.projectName)}`)),console.log(E.gray(`Run: ${[e.commandPlan.command,...e.commandPlan.args].join(" ")}`)),console.log(E.gray(`Default: http://localhost:${e.definition.defaultPort}`));}function vo(){let e=Number.parseInt(process.versions.node.split(".")[0]??"",10);return Number.isFinite(e)?e:0}function Ao(e){if(!e.minNodeVersion&&!e.minNodeMajor||(e.minNodeVersion?xo(process.versions.node,e.minNodeVersion)>=0:vo()>=(e.minNodeMajor??0)))return;let r=e.minNodeVersion??`${e.minNodeMajor}+`;throw new Error(e.minNodeMessage??`${e.displayName} requires Node.js ${r} (current: ${process.versions.node}).`)}function xo(e,t){let r=e.split(".").map(s=>Number.parseInt(s,10)||0),o=t.split(".").map(s=>Number.parseInt(s,10)||0),a=Math.max(r.length,o.length);for(let s=0;s<a;s+=1){let n=(r[s]??0)-(o[s]??0);if(n!==0)return n}return 0}async function Ro(e){if(!await M.pathExists(e))return false;let t=x.join(e,"package.json");if(!await M.pathExists(t))return false;try{let r=await M.readJson(t);return r!==null&&typeof r=="object"&&!Array.isArray(r)}catch{return false}}async function bo(e){let t=x.join(e,".git");if(await M.pathExists(t))return;if(await b$1(e)){console.log(E.gray(" Git initialization skipped; target is inside an existing git worktree."));return}if(await ir("git",["init"],e)===0){console.log(E.gray(" Git repository initialized."));return}console.log(E.yellow("\u26A0\uFE0F Git initialization was skipped or failed. You can run `git init` manually inside the project."));}async function ir(e$1,t,r){let o=e(e$1);return await new Promise(a$1=>{let s=spawn(o.command,[...o.prefixArgs,...t],{cwd:r,stdio:"inherit",shell:false,env:e$1==="git"?a(h$3()):h$3()});s.on("close",n=>a$1(n??1)),s.on("error",()=>a$1(1));})}function To(e,t){let r=e.indexOf(t);if(r>=0&&r+1<e.length)return e[r+1];let o=e.find(a=>a.startsWith(`${t}=`));return o?o.slice(t.length+1):void 0}var dr=[{id:"tauri",kitId:"desktop.tauri",aliases:["desktop.tauri","tauri","tauri-app"],displayName:"Tauri",category:"desktop",kind:"desktop",runtime:"node",runtimeCandidates:["node","rust"],framework:"tauri",supportTier:"extended",versionPolicy:"latest-stable",defaultPort:1420,officialSource:"create-tauri-app",supportsSkipInstall:true,requiredTools:[{command:"rustc",args:["--version"],label:"Rust toolchain",guidance:"Install the current stable Rust toolchain from https://rustup.rs/ and retry."},{command:"cargo",args:["--version"],label:"Cargo",guidance:"Install Cargo through the current stable Rust toolchain and retry."}],commandDisplay:e=>`npm create tauri-app@latest ${e} -- --manager npm --template vanilla-ts --yes`,commandExec:e=>({command:"npm",args:["create","tauri-app@latest",e,"--","--manager","npm","--template","vanilla-ts","--yes"]}),requiredArtifacts:["package.json","src-tauri/Cargo.toml","src-tauri/tauri.conf.json"]},{id:"electron",kitId:"desktop.electron",aliases:["desktop.electron","electron","electron-forge"],displayName:"Electron Forge",category:"desktop",kind:"desktop",runtime:"node",runtimeCandidates:["node"],framework:"electron",supportTier:"extended",versionPolicy:"latest-stable",officialSource:"create-electron-app",supportsSkipInstall:false,requiredTools:[{command:"git",args:["--version"],label:"Git",guidance:"Install Git and make it available on PATH, then retry."}],commandDisplay:e=>`npx create-electron-app@latest ${e} --template vite --typescript`,commandExec:e=>({command:"npx",args:["--yes","create-electron-app@latest",e,"--template","vite","--typescript"]}),requiredArtifacts:["package.json"]},{id:"vscode-extension",kitId:"extension.vscode",aliases:["extension.vscode","vscode","vscode-extension","visual-studio-code-extension"],displayName:"VS Code Extension",category:"extension",kind:"extension",runtime:"node",runtimeCandidates:["node"],framework:"vscode-extension",supportTier:"extended",versionPolicy:"latest-stable",officialSource:"generator-code",supportsSkipInstall:false,nodeSupport:{requirement:"^22.22.2 || ^24.15.0 || >=26.0.0",ranges:[{major:22,minVersion:"22.22.2"},{major:24,minVersion:"24.15.0"},{minVersion:"26.0.0"}],guidance:"Install Node.js 22.22.2+, 24.15.0+, or 26+ before running the latest stable VS Code Extension generator."},requiredTools:[{command:"git",args:["--version"],label:"Git",guidance:"Install Git to initialize source control, or create the project with --skip-git."}],commandDisplay:e=>`npx --package yo@latest --package generator-code@latest -- yo code ${e} --quick --extensionType ts --pkgManager npm --bundle esbuild`,commandExec:(e,t)=>({command:"npx",args:["--yes","--package","yo@latest","--package","generator-code@latest","--","yo","code",e,"--quick","--extensionType","ts","--extensionId",e,"--pkgManager","npm","--bundle","esbuild",`--gitInit=${t.skipGit?"false":"true"}`]}),requiredArtifacts:["package.json","src/extension.ts"]},{id:"laravel",kitId:"php.laravel",aliases:["php.laravel","laravel","php-laravel"],displayName:"Laravel",category:"backend",kind:"backend",runtime:"php",runtimeCandidates:["php","node"],framework:"laravel",supportTier:"extended",versionPolicy:"latest-stable",defaultPort:8e3,officialSource:"composer-create-project",supportsSkipInstall:false,requiredTools:[{command:"php",args:["--version"],label:"PHP",guidance:"Install a PHP version supported by the latest stable Laravel release and retry."},{command:"composer",args:["--version"],label:"Composer",guidance:"Install the latest stable Composer release and retry."}],recommendedTools:[{command:"npm",args:["--version"],label:"npm",guidance:"Install a supported Node.js LTS runtime when you want to build Laravel frontend assets."}],commandDisplay:e=>`composer create-project --no-interaction --prefer-dist --stability=stable laravel/laravel ${e}`,commandExec:e=>({command:"composer",args:["create-project","--no-interaction","--prefer-dist","--stability=stable","laravel/laravel",e]}),requiredArtifacts:["composer.json","artisan"]}],Ce=new Map;for(let e of dr){Ce.set(e.id,e),Ce.set(e.kitId,e);for(let t of e.aliases)Ce.set(t.toLowerCase(),e);}function fs(){return [...dr]}function yt(e){return e?Ce.get(e.trim().toLowerCase())??null:null}function gs(e){return yt(e)!==null}function Oo(e){return `workspai create project ${yt(e)?.kitId??"<desktop.tauri|desktop.electron|extension.vscode|php.laravel>"} <name> [--output <dir>] [--skip-git] [--skip-install] [--dry-run]`}async function _s(e){if(e[0]!=="create"||e[1]!=="project")throw new Error("Official project create expects: create project <official.kit> <project-name>");let t=yt(e[2]);if(!t)throw new Error(`Unknown official project generator: ${e[2]??"(missing)"}`);let r=e[3];if(!r)throw new Error(`Usage: ${Oo(t.kitId)}`);Pe(r);let o=Go(e,"--output")||process.cwd(),a=x.resolve(o,r),s=e.includes("--dry-run"),n={skipGit:e.includes("--skip-git")||e.includes("--no-git"),skipInstall:e.includes("--skip-install")};if(n.skipInstall&&!t.supportsSkipInstall)throw new Error(`${t.displayName}'s official generator does not support --skip-install. Remove the flag so Workspai can verify the official scaffold.`);let l=t.commandExec(r,n),c=t.commandDisplay(r,n);if(await M.pathExists(a))throw new Error(`Directory "${a}" already exists`);if(s)return Mo(t,a,c,l),{definition:t,projectName:r,projectPath:a,dryRun:s,commandDisplay:c,commandExec:[l.command,...l.args]};Co(t),await Io(t,n),await M.ensureDir(x.dirname(a));try{let i=await No(l,x.dirname(a));if(i!==0)throw new Error(`Official ${t.displayName} generator failed with exit code ${i}`);await Po(t,a),n.skipGit&&await M.remove(x.join(a,".git")),await So({definition:t,projectName:r,projectPath:a,commandDisplay:c,commandExec:[l.command,...l.args],flags:n});}catch(i){throw await M.remove(a).catch(()=>{}),i}return console.log(E.green(`
102
102
  \u2713 ${t.displayName} scaffold ready`)),console.log(E.gray(` Location ${a}`)),console.log(E.gray(` Generator ${t.officialSource} \xB7 ${t.versionPolicy}`)),console.log(E.green(" \u2713 Required scaffold files verified")),console.log(E.green(" \u2713 Generator and version evidence recorded in .workspai/")),t.id==="vscode-extension"&&console.log(E.yellow(" Note Dependency warnings above come from the official generated project; Workspai keeps them visible for Doctor and CI.")),console.log(E.gray(` Next cd ${r} && npx workspai doctor project`)),{definition:t,projectName:r,projectPath:a,dryRun:s,commandDisplay:c,commandExec:[l.command,...l.args]}}async function Po(e,t){let r=[];for(let o of e.requiredArtifacts)await M.pathExists(x.join(t,o))||r.push(o);if(r.length>0)throw new Error(`Official ${e.displayName} generator did not produce required artifacts: ${r.join(", ")}`)}async function So(e){let t=new Date().toISOString(),r=h$2(),o=await Ne(e.projectPath,e.definition.id==="laravel"?"stable":"latest"),a={schema_version:"1.0",name:e.projectName,slug:e.projectName,kind:e.definition.kind,project_type:e.definition.category,category:e.definition.category,runtime:e.definition.runtime,runtime_candidates:e.definition.runtimeCandidates,framework:e.definition.framework,framework_display_name:e.definition.displayName,kit_name:e.definition.kitId,kit:e.definition.kitId,engine:"npm",support_tier:e.definition.supportTier,version_policy:e.definition.versionPolicy,module_support:false,modules:[],workspai_version:r,rapidkit_version:r,generated_by:"workspai",generated_at:t,generator:{id:e.definition.id,source:e.definition.officialSource,official:true,version_policy:e.definition.versionPolicy,version_evidence:o,command_display:e.commandDisplay,command_exec:e.commandExec,skip_install:e.flags.skipInstall,skip_git:e.flags.skipGit},...e.definition.defaultPort?{ports:[{name:"http",port:e.definition.defaultPort,protocol:"http"}]}:{},contracts:{owns:[],apis:[],publishes:[],consumes:[],dependsOn:[],env:[]}},s={engine:"npm",project:e.projectName,kind:e.definition.kind,category:e.definition.category,runtime:e.definition.runtime,runtimeCandidates:e.definition.runtimeCandidates,framework:e.definition.framework,source:"official-generator",versionPolicy:e.definition.versionPolicy},n={schema_version:"1.0",kind:"workspai.official_project_create",generated_at:t,project:{name:e.projectName,path:e.projectPath,kind:e.definition.kind,category:e.definition.category,runtime:e.definition.runtime,runtime_candidates:e.definition.runtimeCandidates,framework:e.definition.framework,kit_name:e.definition.kitId},generator:a.generator,verified_artifacts:e.definition.requiredArtifacts};for(let[l,c]of [["project.json",a],["context.json",s],["official-create.json",n]]){let i=v(e.projectPath,l);await M.ensureDir(x.dirname(i)),await M.writeJson(i,c,{spaces:2});}}async function No(e$1,t){let r=e(e$1.command);return await new Promise(o=>{let a$1=h$3(),s=spawn(r.command,[...r.prefixArgs,...e$1.args],{cwd:t,stdio:"inherit",shell:c()&&r.command===e$1.command&&r.prefixArgs.length===0,env:e$1.command==="git"?a({...a$1,...e$1.env}):{...a$1,...e$1.env}});s.on("close",n=>o(n??1)),s.on("error",()=>o(1));})}async function Io(e,t){let r=e.id==="vscode-extension"&&t.skipGit?[]:[...e.requiredTools],o=e.id==="vscode-extension"&&t.skipGit?[]:e.recommendedTools??[],a=[],s=[];for(let n of r)await lr(n)||a.push(n);for(let n of o)await lr(n)||s.push(n);if(a.length>0){let n=a.map(l=>`- ${l.label}: ${l.guidance}`).join(`
103
103
  `);throw new Error(`${e.displayName} cannot use its latest stable official generator because required local tools are unavailable:
104
104
  ${n}`)}for(let n of s)console.warn(E.yellow(`\u26A0 ${n.label} was not found. ${n.guidance}`));}function Co(e,t=process.versions.node){let r=e.nodeSupport;if(!r)return;let o=Number.parseInt(t.split(".")[0]??"",10);if(!r.ranges.some(s=>(s.major===void 0||s.major===o)&&jo(t,s.minVersion)>=0))throw new Error(`${e.displayName} requires Node.js ${r.requirement} (current: ${t}). ${r.guidance}`)}function jo(e,t){let r=e.split(".").map(s=>Number.parseInt(s,10)||0),o=t.split(".").map(s=>Number.parseInt(s,10)||0),a=Math.max(r.length,o.length);for(let s=0;s<a;s+=1){let n=(r[s]??0)-(o[s]??0);if(n!==0)return n}return 0}async function lr(e$1){let t=e(e$1.command);return await new Promise(r=>{let o=spawn(t.command,[...t.prefixArgs,...e$1.args],{stdio:"ignore",shell:c()&&t.command===e$1.command&&t.prefixArgs.length===0,env:h$3()}),a=false,s=setTimeout(()=>{a||(a=true,o.kill(),r(false));},5e3);o.on("close",n=>{a||(a=true,clearTimeout(s),r(n===0));}),o.on("error",()=>{a||(a=true,clearTimeout(s),r(false));});})}function Mo(e,t,r,o){console.log(E.bold(`
@@ -398,7 +398,7 @@ export function readUserPrompt(argv = process.argv.slice(2)): string {
398
398
  const piped = readFileSync(0, 'utf8').trim();
399
399
  return piped || WORKSPAI_DEFAULT_PROMPT;
400
400
  }
401
- `}var wt={schemaVersion:"workspai.agent-framework-version-baselines.v1",baselines:[{adapterId:"microsoft-agent-framework-python",frameworkId:"microsoft-agent-framework",runtime:"python",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"1.18.0",packages:[{ecosystem:"pypi",name:"agent-framework-core",version:"1.18.0",channel:"stable",role:"framework-core",registryUrl:"https://pypi.org/pypi/agent-framework-core/json"},{ecosystem:"pypi",name:"agent-framework-foundry",version:"1.13.0",channel:"stable",role:"framework-integration",registryUrl:"https://pypi.org/pypi/agent-framework-foundry/json"},{ecosystem:"pypi",name:"azure-identity",version:"1.25.3",channel:"stable",role:"runtime-support",registryUrl:"https://pypi.org/pypi/azure-identity/json"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"microsoft-agent-framework-dotnet",frameworkId:"microsoft-agent-framework",runtime:"dotnet",policy:"latest-admitted",releaseChannel:"preview",frameworkVersion:"1.21.0",packages:[{ecosystem:"nuget",name:"Microsoft.Agents.AI",version:"1.21.0",channel:"stable",role:"framework-core",registryUrl:"https://api.nuget.org/v3-flatcontainer/microsoft.agents.ai/index.json"},{ecosystem:"nuget",name:"Microsoft.Agents.AI.Foundry",version:"1.21.0-preview.260911.1",channel:"preview",role:"framework-integration",registryUrl:"https://api.nuget.org/v3-flatcontainer/microsoft.agents.ai.foundry/index.json"},{ecosystem:"nuget",name:"Azure.Identity",version:"1.21.0",channel:"stable",role:"runtime-support",registryUrl:"https://api.nuget.org/v3-flatcontainer/azure.identity/index.json"},{ecosystem:"nuget",name:"Microsoft.NET.Test.Sdk",version:"18.10.0",channel:"stable",role:"runtime-support",registryUrl:"https://api.nuget.org/v3-flatcontainer/microsoft.net.test.sdk/index.json"},{ecosystem:"nuget",name:"xunit.v3.mtp-v2",version:"4.0.1",channel:"stable",role:"runtime-support",registryUrl:"https://api.nuget.org/v3-flatcontainer/xunit.v3.mtp-v2/index.json"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"openai-agents-python",frameworkId:"openai-agents",runtime:"python",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"0.22.2",packages:[{ecosystem:"pypi",name:"openai-agents",version:"0.22.2",channel:"stable",role:"framework-core",registryUrl:"https://pypi.org/pypi/openai-agents/json"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"openai-agents-typescript",frameworkId:"openai-agents",runtime:"node",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"0.18.0",packages:[{ecosystem:"npm",name:"@openai/agents",version:"0.18.0",channel:"stable",role:"framework-core",registryUrl:"https://registry.npmjs.org/@openai/agents"},{ecosystem:"npm",name:"zod",version:"4.6.5",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/zod"},{ecosystem:"npm",name:"typescript",version:"5.9.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/typescript"},{ecosystem:"npm",name:"@types/node",version:"22.20.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/@types/node"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"google-adk-python",frameworkId:"google-adk",runtime:"python",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"2.9.2",packages:[{ecosystem:"pypi",name:"google-adk",version:"2.9.2",channel:"stable",role:"framework-core",registryUrl:"https://pypi.org/pypi/google-adk/json"}],automaticUpgrade:false,admissionRequired:true,upstream:{githubRepository:"google/adk-python",releaseTagPrefixes:["v"]}},{adapterId:"google-adk-typescript",frameworkId:"google-adk",runtime:"node",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"2.1.0",packages:[{ecosystem:"npm",name:"@google/adk",version:"2.1.0",channel:"stable",role:"framework-core",registryUrl:"https://registry.npmjs.org/@google/adk"},{ecosystem:"npm",name:"zod",version:"4.6.5",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/zod"},{ecosystem:"npm",name:"typescript",version:"5.9.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/typescript"},{ecosystem:"npm",name:"@types/node",version:"22.20.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/@types/node"}],automaticUpgrade:false,admissionRequired:true,upstream:{githubRepository:"google/adk-js",releaseTagPrefixes:["adk-v","main-v"]}}]};var vr=/^\d+(?:\.\d+)*(?:[a-z0-9.-]*)$/i;function Zo(e){if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Agent framework version baseline document must be an object.");let t=e;if(!Array.isArray(t.baselines)||t.baselines.length===0)throw new Error("Agent framework version baseline document contains no adapters.");let r=new Set;for(let o of t.baselines){if(!o||typeof o.adapterId!="string"||typeof o.frameworkId!="string"||o.runtime!=="python"&&o.runtime!=="dotnet"&&o.runtime!=="node"||o.policy!=="latest-admitted"||o.releaseChannel!=="stable"&&o.releaseChannel!=="preview"||!vr.test(o.frameworkVersion)||o.automaticUpgrade!==false||o.admissionRequired!==true||!Array.isArray(o.packages)||o.packages.length===0)throw new Error(`Invalid agent framework version baseline: ${String(o?.adapterId)}`);if(r.has(o.adapterId))throw new Error(`Duplicate agent framework version baseline: ${o.adapterId}`);if(r.add(o.adapterId),o.upstream!==void 0&&(typeof o.upstream.githubRepository!="string"||!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(o.upstream.githubRepository)||!Array.isArray(o.upstream.releaseTagPrefixes)||o.upstream.releaseTagPrefixes.length===0||o.upstream.releaseTagPrefixes.some(n=>typeof n!="string"||n.trim()==="")))throw new Error(`Invalid upstream release metadata in ${o.adapterId}.`);let a=new Set,s=null;for(let n of o.packages){if(!n||n.ecosystem!=="pypi"&&n.ecosystem!=="nuget"&&n.ecosystem!=="npm"||typeof n.name!="string"||!vr.test(n.version)||n.channel!=="stable"&&n.channel!=="preview"||!["framework-core","framework-integration","runtime-support"].includes(n.role)||typeof n.registryUrl!="string"||!n.registryUrl.startsWith("https://"))throw new Error(`Invalid package baseline in ${o.adapterId}.`);if(a.has(n.name))throw new Error(`Duplicate package ${n.name} in ${o.adapterId}.`);if(a.add(n.name),n.role==="framework-core"&&(s=n.version),o.releaseChannel==="stable"&&n.channel!=="stable")throw new Error(`Stable adapter ${o.adapterId} contains a preview dependency.`)}if(!s||s!==o.frameworkVersion)throw new Error(`Framework version is not bound to the core package in ${o.adapterId}.`)}}Zo(wt);var Qo=wt,en=Object.freeze(structuredClone(Qo.baselines).map(e=>Object.freeze({...e,...e.upstream?{upstream:Object.freeze({...e.upstream,releaseTagPrefixes:Object.freeze([...e.upstream.releaseTagPrefixes])})}:{},packages:Object.freeze(e.packages.map(t=>Object.freeze(t)))})));function te(e){let t=en.find(r=>r.adapterId===e);if(!t)throw new Error(`Missing built-in agent framework baseline: ${e}`);return t}var Fe=te("microsoft-agent-framework-python"),re=te("microsoft-agent-framework-dotnet"),kt=te("openai-agents-python"),oe=te("openai-agents-typescript"),_e=te("google-adk-python"),q=te("google-adk-typescript");function y(e,t){let r=e.packages.find(o=>o.name===t);if(!r)throw new Error(`Package is not declared by ${e.adapterId}: ${t}`);return r.version}function Le(e){return /^\d+(?:\.\d+)*(?:\.post\d+)?$/i.test(e.trim())}function tn(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).flatMap(t=>t.match(/\d+|[a-z]+/g)??[]).map(t=>/^\d+$/.test(t)?Number(t):t)}function Ar(e,t){let r=i=>{let p=i.trim().toLowerCase().match(/^(\d+(?:\.\d+)*)(.*)$/);return {core:(p?.[1]??i).split(".").map(_=>Number(_)).filter(_=>Number.isFinite(_)),suffix:tn((p?.[2]??"").replace(/^[.-]+/,"")),stable:Le(i)}},o=r(e),a=r(t),s=Math.max(o.core.length,a.core.length);for(let i=0;i<s;i+=1){let p=(o.core[i]??0)-(a.core[i]??0);if(p!==0)return p}if(o.stable!==a.stable)return o.stable?1:-1;let n=o.suffix,l=a.suffix,c=Math.max(n.length,l.length);for(let i=0;i<c;i+=1){let p=n[i],_=l[i];if(p!==_)return p===void 0?-1:_===void 0?1:typeof p=="number"&&typeof _=="number"?p-_:typeof p=="number"?1:typeof _=="number"?-1:p.localeCompare(_)}return 0}var xr=y(re,"Microsoft.Agents.AI.Foundry"),rn=y(re,"Azure.Identity"),on=y(re,"Microsoft.NET.Test.Sdk"),nn=y(re,"xunit.v3.mtp-v2"),Et=re.frameworkVersion,$=Ge("dotnet",Et,{authoredMarkers:[{id:"dotnet-agent-framework-dependency",kind:"dependency",ecosystem:"nuget",name:"Microsoft.Agents.AI",match:"prefix",manifestPaths:[],manifestSuffixes:[".csproj",".fsproj",".vbproj",".props"],searchDepth:6,weight:1}],generatedMarkers:[{id:"workspai-dotnet-adapter-state",kind:"path",path:".workspai/agent-frameworks/microsoft-agent-framework-dotnet",weight:1}],minimumAuthoredMarkers:1,minimumConfidence:1});function an(e){return e.split("-").filter(Boolean).map(t=>`${t[0].toUpperCase()}${t.slice(1)}`).join("")}function vt(e){let t=b(e),r=an(t);return {slug:t,projectName:r,root:`agents/${t}`,entrypoint:`agents/${t}/Program.cs`,contextLoader:`agents/${t}/WorkspaiContext.cs`,dependencyManifest:`agents/${t}/${r}.csproj`,testProject:`agents/${t}/tests/${r}.Tests.csproj`,test:`agents/${t}/tests/WorkspaiContextTests.cs`,environmentExample:`agents/${t}/.env.example`,readme:`agents/${t}/README.md`,state:`.workspai/agent-frameworks/microsoft-agent-framework-dotnet/${t}.json`}}function Rr(e){let t=vt(e.instanceName);return [d(t.entrypoint,`// Generated and managed by Workspai. Do not place secrets in this file.
401
+ `}var wt={schemaVersion:"workspai.agent-framework-version-baselines.v1",baselines:[{adapterId:"microsoft-agent-framework-python",frameworkId:"microsoft-agent-framework",runtime:"python",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"1.19.0",packages:[{ecosystem:"pypi",name:"agent-framework-core",version:"1.19.0",channel:"stable",role:"framework-core",registryUrl:"https://pypi.org/pypi/agent-framework-core/json"},{ecosystem:"pypi",name:"agent-framework-foundry",version:"1.13.1",channel:"stable",role:"framework-integration",registryUrl:"https://pypi.org/pypi/agent-framework-foundry/json"},{ecosystem:"pypi",name:"azure-identity",version:"1.25.3",channel:"stable",role:"runtime-support",registryUrl:"https://pypi.org/pypi/azure-identity/json"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"microsoft-agent-framework-dotnet",frameworkId:"microsoft-agent-framework",runtime:"dotnet",policy:"latest-admitted",releaseChannel:"preview",frameworkVersion:"1.22.0",packages:[{ecosystem:"nuget",name:"Microsoft.Agents.AI",version:"1.22.0",channel:"stable",role:"framework-core",registryUrl:"https://api.nuget.org/v3-flatcontainer/microsoft.agents.ai/index.json"},{ecosystem:"nuget",name:"Microsoft.Agents.AI.Foundry",version:"1.22.0-preview.260918.1",channel:"preview",role:"framework-integration",registryUrl:"https://api.nuget.org/v3-flatcontainer/microsoft.agents.ai.foundry/index.json"},{ecosystem:"nuget",name:"Azure.Identity",version:"1.21.0",channel:"stable",role:"runtime-support",registryUrl:"https://api.nuget.org/v3-flatcontainer/azure.identity/index.json"},{ecosystem:"nuget",name:"Microsoft.NET.Test.Sdk",version:"18.10.1",channel:"stable",role:"runtime-support",registryUrl:"https://api.nuget.org/v3-flatcontainer/microsoft.net.test.sdk/index.json"},{ecosystem:"nuget",name:"xunit.v3.mtp-v2",version:"4.0.1",channel:"stable",role:"runtime-support",registryUrl:"https://api.nuget.org/v3-flatcontainer/xunit.v3.mtp-v2/index.json"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"openai-agents-python",frameworkId:"openai-agents",runtime:"python",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"0.22.3",packages:[{ecosystem:"pypi",name:"openai-agents",version:"0.22.3",channel:"stable",role:"framework-core",registryUrl:"https://pypi.org/pypi/openai-agents/json"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"openai-agents-typescript",frameworkId:"openai-agents",runtime:"node",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"0.18.0",packages:[{ecosystem:"npm",name:"@openai/agents",version:"0.18.0",channel:"stable",role:"framework-core",registryUrl:"https://registry.npmjs.org/@openai/agents"},{ecosystem:"npm",name:"zod",version:"4.6.5",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/zod"},{ecosystem:"npm",name:"typescript",version:"7.0.2",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/typescript"},{ecosystem:"npm",name:"@types/node",version:"26.6.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/@types/node"}],automaticUpgrade:false,admissionRequired:true},{adapterId:"google-adk-python",frameworkId:"google-adk",runtime:"python",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"2.10.0",packages:[{ecosystem:"pypi",name:"google-adk",version:"2.10.0",channel:"stable",role:"framework-core",registryUrl:"https://pypi.org/pypi/google-adk/json"}],automaticUpgrade:false,admissionRequired:true,upstream:{githubRepository:"google/adk-python",releaseTagPrefixes:["v"]}},{adapterId:"google-adk-typescript",frameworkId:"google-adk",runtime:"node",policy:"latest-admitted",releaseChannel:"stable",frameworkVersion:"2.1.0",packages:[{ecosystem:"npm",name:"@google/adk",version:"2.1.0",channel:"stable",role:"framework-core",registryUrl:"https://registry.npmjs.org/@google/adk"},{ecosystem:"npm",name:"zod",version:"4.6.5",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/zod"},{ecosystem:"npm",name:"typescript",version:"7.0.2",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/typescript"},{ecosystem:"npm",name:"@types/node",version:"26.6.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/@types/node"}],automaticUpgrade:false,admissionRequired:true,upstream:{githubRepository:"google/adk-js",releaseTagPrefixes:["adk-v","main-v"]}}]};var vr=/^\d+(?:\.\d+)*(?:[a-z0-9.-]*)$/i;function Zo(e){if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Agent framework version baseline document must be an object.");let t=e;if(!Array.isArray(t.baselines)||t.baselines.length===0)throw new Error("Agent framework version baseline document contains no adapters.");let r=new Set;for(let o of t.baselines){if(!o||typeof o.adapterId!="string"||typeof o.frameworkId!="string"||o.runtime!=="python"&&o.runtime!=="dotnet"&&o.runtime!=="node"||o.policy!=="latest-admitted"||o.releaseChannel!=="stable"&&o.releaseChannel!=="preview"||!vr.test(o.frameworkVersion)||o.automaticUpgrade!==false||o.admissionRequired!==true||!Array.isArray(o.packages)||o.packages.length===0)throw new Error(`Invalid agent framework version baseline: ${String(o?.adapterId)}`);if(r.has(o.adapterId))throw new Error(`Duplicate agent framework version baseline: ${o.adapterId}`);if(r.add(o.adapterId),o.upstream!==void 0&&(typeof o.upstream.githubRepository!="string"||!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(o.upstream.githubRepository)||!Array.isArray(o.upstream.releaseTagPrefixes)||o.upstream.releaseTagPrefixes.length===0||o.upstream.releaseTagPrefixes.some(n=>typeof n!="string"||n.trim()==="")))throw new Error(`Invalid upstream release metadata in ${o.adapterId}.`);let a=new Set,s=null;for(let n of o.packages){if(!n||n.ecosystem!=="pypi"&&n.ecosystem!=="nuget"&&n.ecosystem!=="npm"||typeof n.name!="string"||!vr.test(n.version)||n.channel!=="stable"&&n.channel!=="preview"||!["framework-core","framework-integration","runtime-support"].includes(n.role)||typeof n.registryUrl!="string"||!n.registryUrl.startsWith("https://"))throw new Error(`Invalid package baseline in ${o.adapterId}.`);if(a.has(n.name))throw new Error(`Duplicate package ${n.name} in ${o.adapterId}.`);if(a.add(n.name),n.role==="framework-core"&&(s=n.version),o.releaseChannel==="stable"&&n.channel!=="stable")throw new Error(`Stable adapter ${o.adapterId} contains a preview dependency.`)}if(!s||s!==o.frameworkVersion)throw new Error(`Framework version is not bound to the core package in ${o.adapterId}.`)}}Zo(wt);var Qo=wt,en=Object.freeze(structuredClone(Qo.baselines).map(e=>Object.freeze({...e,...e.upstream?{upstream:Object.freeze({...e.upstream,releaseTagPrefixes:Object.freeze([...e.upstream.releaseTagPrefixes])})}:{},packages:Object.freeze(e.packages.map(t=>Object.freeze(t)))})));function te(e){let t=en.find(r=>r.adapterId===e);if(!t)throw new Error(`Missing built-in agent framework baseline: ${e}`);return t}var Fe=te("microsoft-agent-framework-python"),re=te("microsoft-agent-framework-dotnet"),kt=te("openai-agents-python"),oe=te("openai-agents-typescript"),_e=te("google-adk-python"),q=te("google-adk-typescript");function y(e,t){let r=e.packages.find(o=>o.name===t);if(!r)throw new Error(`Package is not declared by ${e.adapterId}: ${t}`);return r.version}function Le(e){return /^\d+(?:\.\d+)*(?:\.post\d+)?$/i.test(e.trim())}function tn(e){return e.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean).flatMap(t=>t.match(/\d+|[a-z]+/g)??[]).map(t=>/^\d+$/.test(t)?Number(t):t)}function Ar(e,t){let r=i=>{let p=i.trim().toLowerCase().match(/^(\d+(?:\.\d+)*)(.*)$/);return {core:(p?.[1]??i).split(".").map(_=>Number(_)).filter(_=>Number.isFinite(_)),suffix:tn((p?.[2]??"").replace(/^[.-]+/,"")),stable:Le(i)}},o=r(e),a=r(t),s=Math.max(o.core.length,a.core.length);for(let i=0;i<s;i+=1){let p=(o.core[i]??0)-(a.core[i]??0);if(p!==0)return p}if(o.stable!==a.stable)return o.stable?1:-1;let n=o.suffix,l=a.suffix,c=Math.max(n.length,l.length);for(let i=0;i<c;i+=1){let p=n[i],_=l[i];if(p!==_)return p===void 0?-1:_===void 0?1:typeof p=="number"&&typeof _=="number"?p-_:typeof p=="number"?1:typeof _=="number"?-1:p.localeCompare(_)}return 0}var xr=y(re,"Microsoft.Agents.AI.Foundry"),rn=y(re,"Azure.Identity"),on=y(re,"Microsoft.NET.Test.Sdk"),nn=y(re,"xunit.v3.mtp-v2"),Et=re.frameworkVersion,$=Ge("dotnet",Et,{authoredMarkers:[{id:"dotnet-agent-framework-dependency",kind:"dependency",ecosystem:"nuget",name:"Microsoft.Agents.AI",match:"prefix",manifestPaths:[],manifestSuffixes:[".csproj",".fsproj",".vbproj",".props"],searchDepth:6,weight:1}],generatedMarkers:[{id:"workspai-dotnet-adapter-state",kind:"path",path:".workspai/agent-frameworks/microsoft-agent-framework-dotnet",weight:1}],minimumAuthoredMarkers:1,minimumConfidence:1});function an(e){return e.split("-").filter(Boolean).map(t=>`${t[0].toUpperCase()}${t.slice(1)}`).join("")}function vt(e){let t=b(e),r=an(t);return {slug:t,projectName:r,root:`agents/${t}`,entrypoint:`agents/${t}/Program.cs`,contextLoader:`agents/${t}/WorkspaiContext.cs`,dependencyManifest:`agents/${t}/${r}.csproj`,testProject:`agents/${t}/tests/${r}.Tests.csproj`,test:`agents/${t}/tests/WorkspaiContextTests.cs`,environmentExample:`agents/${t}/.env.example`,readme:`agents/${t}/README.md`,state:`.workspai/agent-frameworks/microsoft-agent-framework-dotnet/${t}.json`}}function Rr(e){let t=vt(e.instanceName);return [d(t.entrypoint,`// Generated and managed by Workspai. Do not place secrets in this file.
402
402
 
403
403
  using System.ComponentModel;
404
404
  using Azure.AI.Projects;
@@ -1489,25 +1489,29 @@ def redact(message: str) -> str:
1489
1489
  async def main() -> None:
1490
1490
  prompt = read_user_prompt()
1491
1491
  agent = build_agent()
1492
- run_stream = getattr(agent, "run_stream", None)
1493
- if callable(run_stream):
1494
- wrote = False
1495
- async for update in run_stream(prompt):
1496
- text = getattr(update, "text", None)
1497
- if isinstance(text, str) and text:
1498
- sys.stdout.write(text)
1499
- sys.stdout.flush()
1500
- wrote = True
1501
- elif update is not None and text is None:
1502
- rendered = str(update)
1503
- if rendered:
1504
- sys.stdout.write(rendered)
1492
+ # Agent owns the Foundry client's async lifecycle. The framework's documented
1493
+ # context-manager boundary closes client and tool resources on success, error,
1494
+ # or cancellation.
1495
+ async with agent:
1496
+ run_stream = getattr(agent, "run_stream", None)
1497
+ if callable(run_stream):
1498
+ wrote = False
1499
+ async for update in run_stream(prompt):
1500
+ text = getattr(update, "text", None)
1501
+ if isinstance(text, str) and text:
1502
+ sys.stdout.write(text)
1505
1503
  sys.stdout.flush()
1506
1504
  wrote = True
1507
- if wrote:
1508
- sys.stdout.write("\\n")
1509
- return
1510
- print(await agent.run(prompt))
1505
+ elif update is not None and text is None:
1506
+ rendered = str(update)
1507
+ if rendered:
1508
+ sys.stdout.write(rendered)
1509
+ sys.stdout.flush()
1510
+ wrote = True
1511
+ if wrote:
1512
+ sys.stdout.write("\\n")
1513
+ return
1514
+ print(await agent.run(prompt))
1511
1515
 
1512
1516
 
1513
1517
  if __name__ == "__main__":
@@ -1809,11 +1813,11 @@ Export \`FOUNDRY_PROJECT_ENDPOINT\` and \`FOUNDRY_MODEL\` in your shell. \`Defau
1809
1813
 
1810
1814
  \`${r} ${t.entrypoint}\`
1811
1815
 
1812
- The starter is a single Foundry agent with three read-only typed tools: \`describe_workspai_context\`, \`read_workspai_project_summary\`, and \`list_workspai_supported_commands\`. It streams stdout through \`run_stream\` when the runtime exposes that method and falls back to \`run\`. Pass a prompt as argv or stdin; a TTY with no argv uses the default summarize prompt. The agent does not paste admitted JSON into instructions. \`boundedGraphSearch\` is a pointer, not a shell. It does not install extra workflow, MCP, sandbox, or hosted-tool packages. Multi-agent orchestration, human-approval loops, and durable sessions are not part of this scaffold.
1816
+ The starter is a single Foundry agent with three read-only typed tools: \`describe_workspai_context\`, \`read_workspai_project_summary\`, and \`list_workspai_supported_commands\`. It enters the framework's documented \`async with agent\` lifecycle boundary so client and tool resources close on success, failure, or cancellation. It streams stdout through \`run_stream\` when the runtime exposes that method and falls back to \`run\`. Pass a prompt as argv or stdin; a TTY with no argv uses the default summarize prompt. The agent does not paste admitted JSON into instructions. \`boundedGraphSearch\` is a pointer, not a shell. It does not install extra workflow, MCP, sandbox, or hosted-tool packages. Multi-agent orchestration, human-approval loops, and durable sessions are not part of this scaffold.
1813
1817
 
1814
1818
  Workspai still owns mutation admission and verification. A successful model run is not verified evidence.
1815
1819
  `),d(t.state,`${JSON.stringify({notice:"Generated and managed by Workspai",schemaVersion:"workspai.agent-framework-instance.v1",adapterId:B.adapter.id,frameworkVersion:We,runtime:"python",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"]},null,2)}
1816
- `)]}function ln(e,t){let r=w(B,Or(t),t.existingFiles,t.ownershipLedger);return P({adapter:Ke,mode:e,adapterInput:t,rendered:r,dependencyRecommendation:{path:At(t.instanceName).dependencyManifest,summary:"Use the isolated agent dependency manifest; do not rewrite the repository root dependency graph."}})}var Ke={manifest:B,detect(e){return A(e,B)},plan:ln,render(e){return w(B,Or(e),e.existingFiles,e.ownershipLedger)},context(e){let t=At(e.instanceName),r=i();return {adapterId:B.adapter.id,frameworkId:B.framework.id,runtime:"python>=3.10",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"],verificationCommands:[`${r} -m compileall ${t.root}`,`cd ${t.root} && ${r} -m unittest discover -s tests`],boundaries:["Workspai remains the canonical workspace and verification authority.","The framework owns conversation and runtime state only.","Model-provider network access and mutating tools require explicit grants."]}},validate(e){return O(Ke,e)},resolveRuntime(e){return S("python",">=3.10",e)}};var cn=new Set(["single-agent","typed-tools","local-execution"]),dn=new Set(["telemetry","provider-neutral-models"]),pn={python:["Generated projects pin openai-agents 0.22.2 and require Python >=3.10.","Tracing defaults off. openai-agents 0.22.2 DefaultTraceProvider treats OPENAI_AGENTS_DISABLE_TRACING=1|true as disabled until set_tracing_disabled is called; the starter therefore never calls set_tracing_disabled(False) when that env is set.","Cancellation uses Runner.max_turns and ModelSettings.timeout from this SDK version. ModelSettings.timeout is per model request, not a host-owned deadline for the whole run.","Context containment uses lstat, realpath, O_NOFOLLOW open when available, and a capped fd read. That is not an atomic path walk and does not prove a TOCTOU-free open against a concurrent replacement of a hop."],typescript:["Generated projects pin @openai/agents 0.18.0, require Node.js >=22, and declare zod 4.6.5 as the SDK peer.","Tracing defaults off. The starter passes Runner({ tracingDisabled }) from WORKSPAI_AGENT_TRACING and OPENAI_AGENTS_DISABLE_TRACING; the disable env wins.","Cancellation uses run maxTurns plus AbortSignal from this SDK version. AbortSignal.timeout(30000) is the SDK run signal for that call, not a separate Workspai timeout service.","Context containment uses lstat, realpath, O_NOFOLLOW open when available, and a capped fd read. That is not an atomic path walk and does not prove a TOCTOU-free open against a concurrent replacement of a hop."]};function mn(e){return Object.fromEntries(V.map(t=>{let r=cn.has(t)?"native":dn.has(t)?"conditional":"unsupported",o=pn[e];return [t,{support:r,evidence:r==="unsupported"?[]:r==="native"?[`OpenAI Agents SDK ${e} starter implements ${t} with the pinned SDK APIs and credentialless conformance.`]:[`OpenAI Agents SDK ${e} documents ${t}; the Workspai adapter exposes it only when declared environment or package prerequisites are present.`],prerequisites:t==="telemetry"?e==="python"?["Opt in with WORKSPAI_AGENT_TRACING=1. OPENAI_AGENTS_DISABLE_TRACING=1|true keeps tracing off even if that opt-in is set. openai-agents 0.22.2 reads OPENAI_AGENTS_DISABLE_TRACING on first trace unless set_tracing_disabled was already called."]:["Opt in with WORKSPAI_AGENT_TRACING=1. OPENAI_AGENTS_DISABLE_TRACING=1|true keeps tracing off even if that opt-in is set. Credentialless runs set the disable env and pass Runner({ tracingDisabled: true })."]:t==="provider-neutral-models"?e==="python"?["Install and configure a documented non-OpenAI model integration for openai-agents 0.22.2. The starter only reads OPENAI_MODEL or OPENAI_DEFAULT_MODEL."]:["Supply a documented non-OpenAI model implementation for @openai/agents 0.18.0. The starter only reads OPENAI_MODEL or OPENAI_DEFAULT_MODEL."]:[],limitations:r==="unsupported"?[]:[...o,...t==="typed-tools"?["The starter ships one bounded read-only context tool. It does not grant shell, filesystem mutation, or unrestricted tool execution."]:[]]}]}))}function Ue(e,t,r){let o=e==="python"?"python":"node",a=`openai-agents-${e}`;return {schemaVersion:U,protocolVersion:L,adapter:{id:a,package:"@workspai/cli",version:"0.1.0",stability:"stable"},framework:{id:"openai-agents",name:"OpenAI Agents SDK",homepage:e==="python"?"https://openai.github.io/openai-agents-python/":"https://openai.github.io/openai-agents-js/",license:"MIT",upstreamStatus:"stable",supportedVersionRange:e==="python"?">=0.22 <1":">=0.18 <1",testedVersions:[t]},implementation:{languages:[e==="python"?"Python":"TypeScript"],runtimes:[o],platforms:["linux","darwin","win32"],executionBoundary:"none",distribution:"bundled"},operations:{detect:{supported:true,mode:"read-only",limitations:[]},"plan-scaffold":{supported:true,mode:"plan-only",limitations:[]},"plan-attach":{supported:true,mode:"plan-only",limitations:["Dependency changes require explicit host admission."]},"render-managed-files":{supported:true,mode:"render-only",limitations:["Rendering never writes files or installs dependencies."]},"project-context":{supported:true,mode:"resolve-only",limitations:[]},validate:{supported:true,mode:"read-only",limitations:["Provider-backed integration checks require an explicit network grant."]},"resolve-runtime":{supported:true,mode:"resolve-only",limitations:[]}},capabilities:mn(e),detection:r,ownership:{canonicalTruth:"workspai",runtimeState:"framework",sessionState:"framework",mutationAdmission:"workspai-pcc",verificationOwner:"workspai-cli",managedWritePolicy:"owned-files-or-managed-sections",conflictPolicy:"preserve-user-content",managedRoots:[".workspai/agent-frameworks","agents"]},security:{secrets:"references-only",network:"deny-unless-explicitly-granted",generatedCodeExecution:"disabled-unless-explicitly-granted",toolMutation:"approval-required",untrustedInput:"isolated",telemetrySensitiveData:"redacted"},bindings:{contextInputs:[h$1,p.agentContext],evidenceInputs:[p.intelligenceRun],mutationGateway:"proof-carrying-change",verificationGateway:"workspace-verify",projectionPolicy:"references-and-bounded-projections-only"}}}var Pr=kt.frameworkVersion,xt=y(kt,"openai-agents"),z=Ue("python",Pr,{authoredMarkers:[{id:"python-openai-agents-dependency",kind:"dependency",ecosystem:"pypi",name:"openai-agents",match:"exact",manifestPaths:["pyproject.toml","requirements.txt","requirements-dev.txt"],manifestSuffixes:[".toml",".txt"],searchDepth:4,weight:1}],generatedMarkers:[{id:"workspai-python-openai-agents-state",kind:"path",path:".workspai/agent-frameworks/openai-agents-python",weight:1}],minimumAuthoredMarkers:1,minimumConfidence:1});function Rt(e){let t=b(e);return {slug:t,root:`agents/${t}`,entrypoint:`agents/${t}/main.py`,context:`agents/${t}/workspai_context.py`,agent:`agents/${t}/agent.py`,dependencyManifest:`agents/${t}/pyproject.toml`,test:`agents/${t}/tests/test_context.py`,frameworkTest:`agents/${t}/tests/test_framework.py`,environmentExample:`agents/${t}/.env.example`,readme:`agents/${t}/README.md`,state:`.workspai/agent-frameworks/openai-agents-python/${t}.json`}}function Sr(e){let t=Rt(e.instanceName),r=i();return [d(t.context,ne()),d(t.agent,`# Generated and managed by Workspai. Do not place secrets in this file.
1820
+ `)]}function ln(e,t){let r=w(B,Or(t),t.existingFiles,t.ownershipLedger);return P({adapter:Ke,mode:e,adapterInput:t,rendered:r,dependencyRecommendation:{path:At(t.instanceName).dependencyManifest,summary:"Use the isolated agent dependency manifest; do not rewrite the repository root dependency graph."}})}var Ke={manifest:B,detect(e){return A(e,B)},plan:ln,render(e){return w(B,Or(e),e.existingFiles,e.ownershipLedger)},context(e){let t=At(e.instanceName),r=i();return {adapterId:B.adapter.id,frameworkId:B.framework.id,runtime:"python>=3.10",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"],verificationCommands:[`${r} -m compileall ${t.root}`,`cd ${t.root} && ${r} -m unittest discover -s tests`],boundaries:["Workspai remains the canonical workspace and verification authority.","The framework owns conversation and runtime state only.","Model-provider network access and mutating tools require explicit grants."]}},validate(e){return O(Ke,e)},resolveRuntime(e){return S("python",">=3.10",e)}};var cn=new Set(["single-agent","typed-tools","local-execution"]),dn=new Set(["telemetry","provider-neutral-models"]),pn={python:["Generated projects pin openai-agents 0.22.3 and require Python >=3.10.","Tracing defaults off. openai-agents 0.22.3 DefaultTraceProvider treats OPENAI_AGENTS_DISABLE_TRACING=1|true as disabled until set_tracing_disabled is called; the starter therefore never calls set_tracing_disabled(False) when that env is set.","Cancellation uses Runner.max_turns and ModelSettings.timeout from this SDK version. ModelSettings.timeout is per model request, not a host-owned deadline for the whole run.","Context containment uses lstat, realpath, O_NOFOLLOW open when available, and a capped fd read. That is not an atomic path walk and does not prove a TOCTOU-free open against a concurrent replacement of a hop."],typescript:["Generated projects pin @openai/agents 0.18.0, require Node.js >=22, and declare zod 4.6.5 as the SDK peer.","Tracing defaults off. The starter passes Runner({ tracingDisabled }) from WORKSPAI_AGENT_TRACING and OPENAI_AGENTS_DISABLE_TRACING; the disable env wins.","Cancellation uses run maxTurns plus AbortSignal from this SDK version. AbortSignal.timeout(30000) is the SDK run signal for that call, not a separate Workspai timeout service.","Context containment uses lstat, realpath, O_NOFOLLOW open when available, and a capped fd read. That is not an atomic path walk and does not prove a TOCTOU-free open against a concurrent replacement of a hop."]};function mn(e){return Object.fromEntries(V.map(t=>{let r=cn.has(t)?"native":dn.has(t)?"conditional":"unsupported",o=pn[e];return [t,{support:r,evidence:r==="unsupported"?[]:r==="native"?[`OpenAI Agents SDK ${e} starter implements ${t} with the pinned SDK APIs and credentialless conformance.`]:[`OpenAI Agents SDK ${e} documents ${t}; the Workspai adapter exposes it only when declared environment or package prerequisites are present.`],prerequisites:t==="telemetry"?e==="python"?["Opt in with WORKSPAI_AGENT_TRACING=1. OPENAI_AGENTS_DISABLE_TRACING=1|true keeps tracing off even if that opt-in is set. openai-agents 0.22.3 reads OPENAI_AGENTS_DISABLE_TRACING on first trace unless set_tracing_disabled was already called."]:["Opt in with WORKSPAI_AGENT_TRACING=1. OPENAI_AGENTS_DISABLE_TRACING=1|true keeps tracing off even if that opt-in is set. Credentialless runs set the disable env and pass Runner({ tracingDisabled: true })."]:t==="provider-neutral-models"?e==="python"?["Install and configure a documented non-OpenAI model integration for openai-agents 0.22.3. The starter only reads OPENAI_MODEL or OPENAI_DEFAULT_MODEL."]:["Supply a documented non-OpenAI model implementation for @openai/agents 0.18.0. The starter only reads OPENAI_MODEL or OPENAI_DEFAULT_MODEL."]:[],limitations:r==="unsupported"?[]:[...o,...t==="typed-tools"?["The starter ships one bounded read-only context tool. It does not grant shell, filesystem mutation, or unrestricted tool execution."]:[]]}]}))}function Ue(e,t,r){let o=e==="python"?"python":"node",a=`openai-agents-${e}`;return {schemaVersion:U,protocolVersion:L,adapter:{id:a,package:"@workspai/cli",version:"0.1.0",stability:"stable"},framework:{id:"openai-agents",name:"OpenAI Agents SDK",homepage:e==="python"?"https://openai.github.io/openai-agents-python/":"https://openai.github.io/openai-agents-js/",license:"MIT",upstreamStatus:"stable",supportedVersionRange:e==="python"?">=0.22 <1":">=0.18 <1",testedVersions:[t]},implementation:{languages:[e==="python"?"Python":"TypeScript"],runtimes:[o],platforms:["linux","darwin","win32"],executionBoundary:"none",distribution:"bundled"},operations:{detect:{supported:true,mode:"read-only",limitations:[]},"plan-scaffold":{supported:true,mode:"plan-only",limitations:[]},"plan-attach":{supported:true,mode:"plan-only",limitations:["Dependency changes require explicit host admission."]},"render-managed-files":{supported:true,mode:"render-only",limitations:["Rendering never writes files or installs dependencies."]},"project-context":{supported:true,mode:"resolve-only",limitations:[]},validate:{supported:true,mode:"read-only",limitations:["Provider-backed integration checks require an explicit network grant."]},"resolve-runtime":{supported:true,mode:"resolve-only",limitations:[]}},capabilities:mn(e),detection:r,ownership:{canonicalTruth:"workspai",runtimeState:"framework",sessionState:"framework",mutationAdmission:"workspai-pcc",verificationOwner:"workspai-cli",managedWritePolicy:"owned-files-or-managed-sections",conflictPolicy:"preserve-user-content",managedRoots:[".workspai/agent-frameworks","agents"]},security:{secrets:"references-only",network:"deny-unless-explicitly-granted",generatedCodeExecution:"disabled-unless-explicitly-granted",toolMutation:"approval-required",untrustedInput:"isolated",telemetrySensitiveData:"redacted"},bindings:{contextInputs:[h$1,p.agentContext],evidenceInputs:[p.intelligenceRun],mutationGateway:"proof-carrying-change",verificationGateway:"workspace-verify",projectionPolicy:"references-and-bounded-projections-only"}}}var Pr=kt.frameworkVersion,xt=y(kt,"openai-agents"),z=Ue("python",Pr,{authoredMarkers:[{id:"python-openai-agents-dependency",kind:"dependency",ecosystem:"pypi",name:"openai-agents",match:"exact",manifestPaths:["pyproject.toml","requirements.txt","requirements-dev.txt"],manifestSuffixes:[".toml",".txt"],searchDepth:4,weight:1}],generatedMarkers:[{id:"workspai-python-openai-agents-state",kind:"path",path:".workspai/agent-frameworks/openai-agents-python",weight:1}],minimumAuthoredMarkers:1,minimumConfidence:1});function Rt(e){let t=b(e);return {slug:t,root:`agents/${t}`,entrypoint:`agents/${t}/main.py`,context:`agents/${t}/workspai_context.py`,agent:`agents/${t}/agent.py`,dependencyManifest:`agents/${t}/pyproject.toml`,test:`agents/${t}/tests/test_context.py`,frameworkTest:`agents/${t}/tests/test_framework.py`,environmentExample:`agents/${t}/.env.example`,readme:`agents/${t}/README.md`,state:`.workspai/agent-frameworks/openai-agents-python/${t}.json`}}function Sr(e){let t=Rt(e.instanceName),r=i();return [d(t.context,ne()),d(t.agent,`# Generated and managed by Workspai. Do not place secrets in this file.
1817
1821
 
1818
1822
  from __future__ import annotations
1819
1823
 
@@ -2759,6 +2763,7 @@ from agent import (
2759
2763
  require_model_name,
2760
2764
  require_provider_profile,
2761
2765
  )
2766
+ from google.adk.apps import App
2762
2767
  from google.adk.agents.run_config import RunConfig, StreamingMode
2763
2768
  from google.adk.runners import Runner
2764
2769
  from google.adk.sessions import InMemorySessionService
@@ -2866,34 +2871,38 @@ async def run_admitted_agent(
2866
2871
  service = session_service or InMemorySessionService()
2867
2872
  agent = build_agent(model=model)
2868
2873
  app_name = agent.name
2869
- runner = Runner(agent=agent, app_name=app_name, session_service=service)
2870
- existing = (
2871
- await service.get_session(app_name=app_name, user_id=APP_USER, session_id=session_id)
2872
- if session_id
2873
- else None
2874
- )
2875
- session = existing or await service.create_session(
2876
- app_name=app_name, user_id=APP_USER, session_id=session_id
2877
- )
2878
- config = RunConfig(
2879
- streaming_mode=StreamingMode.SSE if streaming else StreamingMode.NONE,
2880
- max_llm_calls=MAX_LLM_CALLS if max_llm_calls is None else max_llm_calls,
2881
- )
2882
- message = types.Content(role="user", parts=[types.Part(text=prompt)])
2883
- state = _StreamState()
2884
-
2885
- async def _consume() -> str:
2886
- async for event in runner.run_async(
2887
- user_id=APP_USER,
2888
- session_id=session.id,
2889
- new_message=message,
2890
- run_config=config,
2891
- ):
2892
- _observe_stream_event(state, event, on_text)
2893
- return state.result()
2874
+ app = App(name=app_name, root_agent=agent)
2875
+ runner = Runner(app=app, session_service=service)
2876
+ # Runner is the ADK lifecycle boundary. Its async context closes plugins and
2877
+ # flushes services even when the host timeout cancels event consumption.
2878
+ async with runner:
2879
+ existing = (
2880
+ await service.get_session(app_name=app_name, user_id=APP_USER, session_id=session_id)
2881
+ if session_id
2882
+ else None
2883
+ )
2884
+ session = existing or await service.create_session(
2885
+ app_name=app_name, user_id=APP_USER, session_id=session_id
2886
+ )
2887
+ config = RunConfig(
2888
+ streaming_mode=StreamingMode.SSE if streaming else StreamingMode.NONE,
2889
+ max_llm_calls=MAX_LLM_CALLS if max_llm_calls is None else max_llm_calls,
2890
+ )
2891
+ message = types.Content(role="user", parts=[types.Part(text=prompt)])
2892
+ state = _StreamState()
2893
+
2894
+ async def _consume() -> str:
2895
+ async for event in runner.run_async(
2896
+ user_id=APP_USER,
2897
+ session_id=session.id,
2898
+ new_message=message,
2899
+ run_config=config,
2900
+ ):
2901
+ _observe_stream_event(state, event, on_text)
2902
+ return state.result()
2894
2903
 
2895
- timeout = RUN_TIMEOUT_SECONDS if timeout_seconds is None else timeout_seconds
2896
- return await asyncio.wait_for(_consume(), timeout=timeout)
2904
+ timeout = RUN_TIMEOUT_SECONDS if timeout_seconds is None else timeout_seconds
2905
+ return await asyncio.wait_for(_consume(), timeout=timeout)
2897
2906
 
2898
2907
 
2899
2908
  def _write_stdout_delta(delta: str) -> None:
@@ -3670,9 +3679,9 @@ Export \`WORKSPAI_ADK_PROVIDER\`, \`ADK_MODEL\`, and the matching provider crede
3670
3679
 
3671
3680
  \`${o} ${t.entrypoint}\`
3672
3681
 
3673
- Do not depend on a globally installed \`adk\` CLI. The live path uses \`Runner.run_async\` with \`RunConfig(max_llm_calls=8)\` and \`asyncio.wait_for(..., 30)\`. The pinned Python SDK has no AbortSignal on \`run_async\`; host cancellation uses asyncio task cancel.
3682
+ Do not depend on a globally installed \`adk\` CLI. The live path creates an \`App(root_agent=...)\`, constructs \`Runner(app=...)\`, and enters the runner's async context so plugin and service lifecycle cleanup also runs after cancellation. It uses \`Runner.run_async\` with \`RunConfig(max_llm_calls=8)\` and \`asyncio.wait_for(..., 30)\`. The pinned Python SDK has no AbortSignal on \`run_async\`; host cancellation uses asyncio task cancel.
3674
3683
  `),d(t.state,`${JSON.stringify({notice:"Generated and managed by Workspai",schemaVersion:"workspai.agent-framework-instance.v1",adapterId:X.adapter.id,frameworkVersion:Mr,runtime:"python",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],providerProfiles:["gemini-api","vertex-ai"]},null,2)}
3675
- `)]}function Rn(e,t){let r=w(X,Gr(t),t.existingFiles,t.ownershipLedger),o=P({adapter:Xe,mode:e,adapterInput:t,rendered:r,dependencyRecommendation:{path:Pt(t.instanceName).dependencyManifest,summary:"Use the isolated agent dependency manifest; do not rewrite the repository root dependency graph."}}),a=[...o.blockers,...xn(e,t)];return a.length===0?o:{...o,status:"blocked",blockers:a}}var Xe={manifest:X,detect(e){return A(e,X)},plan:Rn,render(e){return w(X,Gr(e),e.existingFiles,e.ownershipLedger)},context(e){let t=Pt(e.instanceName),r=i();return {adapterId:X.adapter.id,frameworkId:X.framework.id,runtime:"python>=3.10",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],verificationCommands:[`${r} -m compileall ${t.root}`,`cd ${t.root} && ${r} -m unittest discover -s tests`],boundaries:["Workspai remains the canonical workspace and verification authority.","Google ADK owns the agent loop, tool dispatch, and in-memory session state only.","gemini-api and vertex-ai are provider profiles, not Workspai gateway kits.","Model-provider network access and mutating tools require explicit grants."]}},validate(e){return O(Xe,e)},resolveRuntime(e){return S("python",">=3.10",e)}};var Lr=q.frameworkVersion,Dr=y(q,"@google/adk"),Fr=y(q,"zod"),bn=y(q,"typescript"),Tn=y(q,"@types/node"),H=Be("typescript",Lr,{authoredMarkers:[{id:"typescript-google-adk-dependency",kind:"dependency",ecosystem:"npm",name:"@google/adk",match:"exact",manifestPaths:["package.json"],manifestSuffixes:[".json"],searchDepth:4,weight:1}],generatedMarkers:[{id:"workspai-typescript-google-adk-state",kind:"path",path:".workspai/agent-frameworks/google-adk-typescript",weight:1}],minimumAuthoredMarkers:1,minimumConfidence:1});function St(e){let t=b(e);return {slug:t,root:`agents/${t}`,entrypoint:`agents/${t}/src/main.ts`,context:`agents/${t}/src/workspai-context.ts`,tracing:`agents/${t}/src/tracing.ts`,agent:`agents/${t}/src/agent.ts`,dependencyManifest:`agents/${t}/package.json`,tsconfig:`agents/${t}/tsconfig.json`,test:`agents/${t}/tests/context.test.ts`,frameworkTest:`agents/${t}/tests/framework.test.ts`,environmentExample:`agents/${t}/.env.example`,gitignore:`agents/${t}/.gitignore`,readme:`agents/${t}/README.md`,state:`.workspai/agent-frameworks/google-adk-typescript/${t}.json`}}function On(e,t){return e!=="attach"?[]:[ze(t.existingFiles),Ye({existingFiles:t.existingFiles,packageName:"@google/adk",minimumInclusive:"2.1",exclusiveMajor:3})].filter(r=>!!r)}function qr(e){let t=St(e.instanceName);return [d(t.context,De()),d(t.tracing,`// Generated and managed by Workspai. Do not place secrets in this file.
3684
+ `)]}function Rn(e,t){let r=w(X,Gr(t),t.existingFiles,t.ownershipLedger),o=P({adapter:Xe,mode:e,adapterInput:t,rendered:r,dependencyRecommendation:{path:Pt(t.instanceName).dependencyManifest,summary:"Use the isolated agent dependency manifest; do not rewrite the repository root dependency graph."}}),a=[...o.blockers,...xn(e,t)];return a.length===0?o:{...o,status:"blocked",blockers:a}}var Xe={manifest:X,detect(e){return A(e,X)},plan:Rn,render(e){return w(X,Gr(e),e.existingFiles,e.ownershipLedger)},context(e){let t=Pt(e.instanceName),r=i();return {adapterId:X.adapter.id,frameworkId:X.framework.id,runtime:"python>=3.10",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],verificationCommands:[`${r} -m compileall ${t.root}`,`cd ${t.root} && ${r} -m unittest discover -s tests`],boundaries:["Workspai remains the canonical workspace and verification authority.","Google ADK owns the agent loop, tool dispatch, and in-memory session state only.","gemini-api and vertex-ai are provider profiles, not Workspai gateway kits.","Model-provider network access and mutating tools require explicit grants."]}},validate(e){return O(Xe,e)},resolveRuntime(e){return S("python",">=3.10",e)}};var Lr=q.frameworkVersion,Dr=y(q,"@google/adk"),Fr=y(q,"zod"),bn=y(q,"typescript"),Tn=y(q,"@types/node"),J=Be("typescript",Lr,{authoredMarkers:[{id:"typescript-google-adk-dependency",kind:"dependency",ecosystem:"npm",name:"@google/adk",match:"exact",manifestPaths:["package.json"],manifestSuffixes:[".json"],searchDepth:4,weight:1}],generatedMarkers:[{id:"workspai-typescript-google-adk-state",kind:"path",path:".workspai/agent-frameworks/google-adk-typescript",weight:1}],minimumAuthoredMarkers:1,minimumConfidence:1});function St(e){let t=b(e);return {slug:t,root:`agents/${t}`,entrypoint:`agents/${t}/src/main.ts`,context:`agents/${t}/src/workspai-context.ts`,tracing:`agents/${t}/src/tracing.ts`,agent:`agents/${t}/src/agent.ts`,dependencyManifest:`agents/${t}/package.json`,tsconfig:`agents/${t}/tsconfig.json`,test:`agents/${t}/tests/context.test.ts`,frameworkTest:`agents/${t}/tests/framework.test.ts`,environmentExample:`agents/${t}/.env.example`,gitignore:`agents/${t}/.gitignore`,readme:`agents/${t}/README.md`,state:`.workspai/agent-frameworks/google-adk-typescript/${t}.json`}}function On(e,t){return e!=="attach"?[]:[ze(t.existingFiles),Ye({existingFiles:t.existingFiles,packageName:"@google/adk",minimumInclusive:"2.1",exclusiveMajor:3})].filter(r=>!!r)}function qr(e){let t=St(e.instanceName);return [d(t.context,De()),d(t.tracing,`// Generated and managed by Workspai. Do not place secrets in this file.
3676
3685
 
3677
3686
  export function tracingEnabled(): boolean {
3678
3687
  return process.env.WORKSPAI_AGENT_TRACING === '1';
@@ -4678,9 +4687,9 @@ Export \`WORKSPAI_ADK_PROVIDER\`, \`ADK_MODEL\`, and the matching provider crede
4678
4687
  \`cd ${t.root} && npm start\`
4679
4688
 
4680
4689
  The live path uses \`Runner.runAsync\` with \`AbortSignal.timeout(30000)\` and \`RunConfig.maxLlmCalls=8\`. Model, limit, and abort failures are rethrown instead of returning an empty string.
4681
- `),d(t.state,`${JSON.stringify({notice:"Generated and managed by Workspai",schemaVersion:"workspai.agent-framework-instance.v1",adapterId:H.adapter.id,frameworkVersion:Lr,runtime:"node",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],providerProfiles:["gemini-api","vertex-ai"]},null,2)}
4682
- `)]}function Pn(e,t){let r=w(H,qr(t),t.existingFiles,t.ownershipLedger),o=P({adapter:He,mode:e,adapterInput:t,rendered:r,dependencyRecommendation:{path:St(t.instanceName).dependencyManifest,summary:"Use the isolated agent dependency manifest; do not rewrite the repository root dependency graph."}}),a=[...o.blockers,...On(e,t)];return a.length===0?o:{...o,status:"blocked",blockers:a}}var He={manifest:H,detect(e){return A(e,H)},plan:Pn,render(e){return w(H,qr(e),e.existingFiles,e.ownershipLedger)},context(e){let t=St(e.instanceName);return {adapterId:H.adapter.id,frameworkId:H.framework.id,runtime:"node>=20.19",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],verificationCommands:[`cd ${t.root} && npm test`],boundaries:["Workspai remains the canonical workspace and verification authority.","Google ADK owns the agent loop, tool dispatch, and in-memory session state only.","gemini-api and vertex-ai are provider profiles, not Workspai gateway kits.","Model-provider network access and mutating tools require explicit grants."]}},validate(e){return O(He,e)},resolveRuntime(e){return S("node",">=20.19",e)}};function Je(e){return `${e.platform}\0${e.runtime}\0${e.frameworkVersion}`}function Sn(e){return `${e.platform}/${e.runtime}/${e.frameworkVersion}`}function Nn(e){return e.implementation.platforms.flatMap(t=>e.implementation.runtimes.flatMap(r=>e.framework.testedVersions.map(o=>({platform:t,runtime:r,frameworkVersion:o}))))}function Nt(e){let t=[];try{a$2(e.manifest,je,"Agent framework manifest");}catch(n){t.push(n instanceof Error?n.message:String(n));}t.push(...Me(e.manifest).map(n=>`manifest: ${n}`));let r=Nn(e.manifest),o=new Map,a=new Set;for(let n of e.reports){let l=false,c={platform:n.environment.platform,runtime:n.environment.runtime,frameworkVersion:n.frameworkVersion},i=Je(c),p=Sn(c);if(a.has(i)){t.push(`duplicate conformance report for ${p}`);continue}a.add(i);try{a$2(n,ur,`Agent framework conformance ${p}`);}catch(I){l=true,t.push(I instanceof Error?I.message:String(I));}for(let I of fr(n))l=true,t.push(`conformance ${p}: ${I}`);(n.adapter.id!==e.manifest.adapter.id||n.adapter.version!==e.manifest.adapter.version)&&(l=true,t.push(`conformance ${p}: adapter identity does not match the manifest`)),n.adapter.manifestSha256!==e.manifestSha256&&(l=true,t.push(`conformance ${p}: manifest digest does not match`));let _=e.implementationSha256ByPlatform?.[c.platform]??e.implementationSha256;(!_||n.adapter.implementationSha256!==_)&&(l=true,t.push(`conformance ${p}: implementation digest does not match`)),e.manifest.implementation.platforms.includes(c.platform)||(l=true,t.push(`conformance ${p}: platform is not advertised by the manifest`)),e.manifest.implementation.runtimes.includes(c.runtime)||(l=true,t.push(`conformance ${p}: runtime is not advertised by the manifest`)),e.manifest.framework.testedVersions.includes(c.frameworkVersion)||(l=true,t.push(`conformance ${p}: framework version is not in the tested baseline`)),n.verdict!=="admitted"&&(l=true,n.blockers.length===0?t.push(`conformance ${p}: report verdict is blocked`):t.push(...n.blockers.map(I=>`conformance ${p}: ${I}`))),n.verdict==="admitted"&&!l&&o.set(i,c);}for(let n of r)o.has(Je(n))||t.push(`missing admitted lane: ${n.platform}/${n.runtime}/${n.frameworkVersion}`);e.manifest.adapter.stability==="experimental"&&t.push("experimental adapters are not selectable"),e.manifest.adapter.stability==="deprecated"&&t.push("deprecated adapters are not selectable");let s=[...new Set(t)].sort();return {status:s.length===0?"admitted":"blocked",requiredLanes:r,admittedLanes:[...o.values()].sort((n,l)=>Je(n).localeCompare(Je(l))),blockers:s}}var Wr={schemaVersion:"workspai.agent-framework-release-admissions.v2",reviewedAt:"2026-09-23T16:11:23.057Z",source:{repository:"chistiq/workspai",commit:"e382ee7f05e092e71ba605daec442265c6f0657c",workflowRunId:35885971480,workflowUrl:"https://github.com/chistiq/workspai/actions/runs/35885971480",conclusion:"success"},adapters:[{id:"microsoft-agent-framework-python",version:"0.1.0",manifestSha256:"e4dfa55256ec2ed7f43927d109b09b541a13217f8a4df964f678fd036bcef060",implementationSha256ByPlatform:{darwin:"828d1fce4903fdfb3f57c219a299ca10dcd88f819be1d4216b3f64e144decff7",linux:"828d1fce4903fdfb3f57c219a299ca10dcd88f819be1d4216b3f64e144decff7",win32:"c6d803516c5c4f8836a474c6e5f6e29d8894e21d1a915d816c713fa522b57e1f"},frameworkVersion:"1.18.0",runtime:"python",platforms:["darwin","linux","win32"]},{id:"microsoft-agent-framework-dotnet",version:"0.1.0",manifestSha256:"1efa079735d51cc3cbbd2d2fe55815850d8aa39f87de4bac4589d9fbfaf2e8e9",implementationSha256ByPlatform:{darwin:"1217f28fdcdacacb2af6bfb9b62053bb4e30fb5a78c41df8e4cb27e03c1aefae",linux:"1217f28fdcdacacb2af6bfb9b62053bb4e30fb5a78c41df8e4cb27e03c1aefae",win32:"1217f28fdcdacacb2af6bfb9b62053bb4e30fb5a78c41df8e4cb27e03c1aefae"},frameworkVersion:"1.21.0",runtime:"dotnet",platforms:["darwin","linux","win32"]},{id:"openai-agents-python",version:"0.1.0",manifestSha256:"f76c9973e07b1a3c7807efc489374cdede84038692d0012e03b530db4bcbba55",implementationSha256ByPlatform:{darwin:"cb707b5264728afba17f81639a6551ca4252316773560fe9cde66436c4318b44",linux:"cb707b5264728afba17f81639a6551ca4252316773560fe9cde66436c4318b44",win32:"ecf4451ed364532f17a0a1677753a4c55f9a16e2f924d9033e270c926a552811"},frameworkVersion:"0.22.2",runtime:"python",platforms:["darwin","linux","win32"]},{id:"openai-agents-typescript",version:"0.1.0",manifestSha256:"35b4e04cda42e227ea8d352e3c87ec1ed7c5f53b39aafb734e895d456a32c38f",implementationSha256ByPlatform:{darwin:"36f76afbf7f9aa06535cb9261a0600dfc61f41658d7a7c9f755d416ae748ba61",linux:"36f76afbf7f9aa06535cb9261a0600dfc61f41658d7a7c9f755d416ae748ba61",win32:"36f76afbf7f9aa06535cb9261a0600dfc61f41658d7a7c9f755d416ae748ba61"},frameworkVersion:"0.18.0",runtime:"node",platforms:["darwin","linux","win32"]},{id:"google-adk-python",version:"0.1.0",manifestSha256:"228ea38c9bd16604420c54701c5c6784979a86aeb965a2254894dadd1fb91059",implementationSha256ByPlatform:{darwin:"fa66e924feaaebc3eaab6767ca3c59a3efe2b3a8c3e9c4bd3c1b561bcc177b8f",linux:"fa66e924feaaebc3eaab6767ca3c59a3efe2b3a8c3e9c4bd3c1b561bcc177b8f",win32:"06fcdaeab5ddf223c48f77b2f14f6462605778e64c5ac53ddbfb8a8df3029979"},frameworkVersion:"2.9.2",runtime:"python",platforms:["darwin","linux","win32"]},{id:"google-adk-typescript",version:"0.1.0",manifestSha256:"be9927f4a04a28632b7396b7fa445c1e3b0da4057179e2665c229ab07332a57d",implementationSha256ByPlatform:{darwin:"ca7a6d7eae2f14fc9ff071b0972f62c80fc9076c2ed5d5ea240267d59a2cdd3a",linux:"ca7a6d7eae2f14fc9ff071b0972f62c80fc9076c2ed5d5ea240267d59a2cdd3a",win32:"ca7a6d7eae2f14fc9ff071b0972f62c80fc9076c2ed5d5ea240267d59a2cdd3a"},frameworkVersion:"2.1.0",runtime:"node",platforms:["darwin","linux","win32"]}]};function It(e){return Array.isArray(e)?e.map(t=>It(t)):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>[t,It(r)])):e}function Ze(e){return createHash("sha256").update(`${JSON.stringify(e.manifest)}
4683
- `).digest("hex")}function Ur(e){let t={projectRoot:"/workspai/qualification/project",instanceName:"qualification-probe",target:{project:"qualification-project",artifactPrefix:"."}},r={algorithm:"workspai.agent-framework-implementation.semantic.v1",adapterId:e.manifest.adapter.id,render:e.render(t),plans:{scaffold:e.plan("scaffold",t),attach:e.plan("attach",t)},context:e.context(t),validation:e.validate(t),runtimeResolution:{unavailable:e.resolveRuntime([]),resolved:e.resolveRuntime([e.manifest.implementation.runtimes[0]]),ambiguous:e.resolveRuntime(e.manifest.implementation.runtimes.map(o=>`${o}@qualification`))}};return createHash("sha256").update(JSON.stringify(It(r))).digest("hex")}var Cn="workspai.agent-framework-release-admissions.v2",ye=["linux","darwin","win32"];function Qe(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function jn(e){if(!Qe(e)||e.schemaVersion!==Cn)throw new Error("Bundled agent framework release admission schema is incompatible.");if(!Qe(e.source))throw new Error("Bundled agent framework release admission source is missing.");if(!/^[a-f0-9]{40}$/.test(String(e.source.commit))||!Number.isSafeInteger(e.source.workflowRunId)||Number(e.source.workflowRunId)<=0||e.source.workflowUrl!==`https://github.com/chistiq/workspai/actions/runs/${String(e.source.workflowRunId)}`||e.source.conclusion!=="success")throw new Error("Bundled agent framework release admission source is invalid.");if(!Array.isArray(e.adapters))throw new Error("Bundled agent framework release admission inventory is invalid.");let t=new Set;return e.adapters.map((r,o)=>{if(!Qe(r))throw new Error(`Bundled agent framework release admission ${o} is invalid.`);let a=String(r.id??""),s=String(r.version??""),n=String(r.manifestSha256??""),l=Qe(r.implementationSha256ByPlatform)?r.implementationSha256ByPlatform:{},c=String(r.frameworkVersion??""),i=String(r.runtime??"");if(!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(a)||!s||!/^[a-f0-9]{64}$/.test(n)||ye.some(_=>!/^[a-f0-9]{64}$/.test(String(l[_]??"")))||!c||!i)throw new Error(`Bundled agent framework release admission ${a||o} is malformed.`);if(t.has(a))throw new Error(`Duplicate bundled agent framework admission: ${a}`);t.add(a);let p=Array.isArray(r.platforms)?r.platforms:[];if(p.length!==ye.length||ye.some(_=>!p.includes(_)))throw new Error(`Bundled agent framework admission has incomplete platforms: ${a}`);return {id:a,version:s,manifestSha256:n,implementationSha256ByPlatform:Object.fromEntries(ye.map(_=>[_,String(l[_])])),frameworkVersion:c,runtime:i,platforms:[...ye]}})}var Mn=Object.freeze(jn(Wr));function Ct(e){let t=Mn.find(s=>s.id===e.manifest.adapter.id);if(!t)return {status:"blocked",admission:null,blockers:[`No reviewed release admission exists for ${e.manifest.adapter.id}.`]};let r=[];t.version!==e.manifest.adapter.version&&r.push("adapter version changed after release admission"),t.manifestSha256!==Ze(e)&&r.push("adapter manifest changed after release admission"),e.manifest.framework.testedVersions.includes(t.frameworkVersion)||r.push("framework baseline changed after release admission"),e.manifest.implementation.runtimes.includes(t.runtime)||r.push("runtime changed after release admission");let o=[...e.manifest.implementation.platforms].sort(),a=[...t.platforms].sort();return JSON.stringify(o)!==JSON.stringify(a)&&r.push("platform matrix changed after release admission"),{status:r.length===0?"admitted":"blocked",admission:structuredClone(t),blockers:r}}function se(e){return e.trim().toLowerCase().replace(/[^a-z0-9.-]+/g,"-")}function W(e){return structuredClone({manifest:e.manifest,manifestSha256:e.manifestSha256,implementationSha256:e.implementationSha256,source:e.source,conformanceReports:e.conformanceReports})}function Gn(e){return {...W(e),...e.releaseAdapter?{releaseAdapter:e.releaseAdapter}:{}}}var et=class{#e=new Map;register(t){a$2(t.manifest,je,`Agent framework adapter ${t.manifest.adapter?.id??"unknown"}`);let r=Me(t.manifest);if(r.length>0)throw new Error(`Cannot register agent framework adapter ${t.manifest.adapter.id}: ${r.join("; ")}`);if(!/^[a-f0-9]{64}$/.test(t.manifestSha256))throw new Error("Agent framework manifest digest must be a lowercase SHA-256 value.");if(!/^[a-f0-9]{64}$/.test(t.implementationSha256))throw new Error("Agent framework implementation digest must be a lowercase SHA-256 value.");let o=se(t.manifest.adapter.id);if(this.#e.has(o))throw new Error(`Agent framework adapter id is already registered: ${o}`);return this.#e.set(o,Gn(t)),this}get(t){let r=this.#e.get(se(t));return r?W(r):null}list(){return [...this.#e.values()].map(W).sort((t,r)=>t.manifest.adapter.id.localeCompare(r.manifest.adapter.id))}resolveAdapter(t){let r=this.#e.get(se(t));if(!r)return {status:"unresolved",entry:null,blockers:[]};let o=r.releaseAdapter?Ct(r.releaseAdapter):null;if(o?.status==="admitted")return {status:"admitted",entry:W(r),blockers:[]};let a=Nt({manifest:r.manifest,manifestSha256:r.manifestSha256,implementationSha256:r.implementationSha256,reports:r.conformanceReports});return a.status==="admitted"?{status:"admitted",entry:W(r),blockers:[]}:{status:"blocked",entry:W(r),blockers:[...o?.blockers??[],...a.blockers]}}async resolveProject(t){let r=t.runtime?se(t.runtime):void 0,o=[...this.#e.values()].filter(i=>!r||i.manifest.implementation.runtimes.some(p=>se(p)===r)),a=await Promise.all(o.map(i=>A(t.projectRoot,i.manifest))),s=a.filter(i=>i.detected);if(s.length===0)return {status:"unresolved",entry:null,candidates:a,blockers:[]};if(s.length>1)return {status:"conflict",entry:null,candidates:a,blockers:[`Multiple agent framework adapters matched authored evidence: ${s.map(i=>i.adapterId).sort().join(", ")}`]};let n=this.#e.get(se(s[0].adapterId));if(!n)throw new Error("Agent framework registry index is inconsistent.");let l=n.releaseAdapter?Ct(n.releaseAdapter):null,c=Nt({manifest:n.manifest,manifestSha256:n.manifestSha256,implementationSha256:n.implementationSha256,reports:n.conformanceReports});return c.status!=="admitted"&&l?.status!=="admitted"?{status:"blocked",entry:W(n),candidates:a,blockers:[...l?.blockers??[],...c.blockers]}:{status:"matched",entry:W(n),candidates:a,blockers:[]}}};var jt=Object.freeze([Ke,qe,Ve,$e,Xe,He]);function Dn(e){return Ze(e)}function Fn(e){return Ur(e)}function Vr(e={},t={}){let r=new et;for(let o of jt)r.register({manifest:o.manifest,manifestSha256:Dn(o),implementationSha256:Fn(o),source:"builtin",conformanceReports:structuredClone(e[o.manifest.adapter.id]??[]),...t.trustReviewedReleaseAdmissions?{releaseAdapter:o}:{}});return r}function le(e){return jt.find(r=>r.manifest.adapter.id===e)?.manifest.adapter.stability==="stable"?"stable":"preview"}var $r=[{id:"agent.microsoft.python",aliases:["agent.microsoft.python","microsoft-agent-python","agent-framework-python"],label:"Microsoft Agent Framework \xB7 Python",runtime:"python",adapterId:"microsoft-agent-framework-python",frameworkId:"microsoft-agent-framework",frameworkName:"Microsoft Agent Framework",requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"],stability:le("microsoft-agent-framework-python")},{id:"agent.microsoft.dotnet",aliases:["agent.microsoft.dotnet","microsoft-agent-dotnet","agent-framework-dotnet"],label:"Microsoft Agent Framework \xB7 .NET",runtime:"dotnet",adapterId:"microsoft-agent-framework-dotnet",frameworkId:"microsoft-agent-framework",frameworkName:"Microsoft Agent Framework",requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"],stability:le("microsoft-agent-framework-dotnet")},{id:"agent.openai.python",aliases:["agent.openai.python","openai-agents-python","openai-agent-python"],label:"OpenAI Agents SDK \xB7 Python",runtime:"python",adapterId:"openai-agents-python",frameworkId:"openai-agents",frameworkName:"OpenAI Agents SDK",requiredEnvironment:["OPENAI_API_KEY","OPENAI_MODEL"],stability:le("openai-agents-python")},{id:"agent.openai.typescript",aliases:["agent.openai.typescript","agent.openai.node","openai-agents-typescript","openai-agent-typescript"],label:"OpenAI Agents SDK \xB7 TypeScript",runtime:"node",adapterId:"openai-agents-typescript",frameworkId:"openai-agents",frameworkName:"OpenAI Agents SDK",requiredEnvironment:["OPENAI_API_KEY","OPENAI_MODEL"],stability:le("openai-agents-typescript")},{id:"agent.google-adk.python",aliases:["agent.google-adk.python","google-adk-python","agent.google.python"],label:"Google Agent Development Kit \xB7 Python",runtime:"python",adapterId:"google-adk-python",frameworkId:"google-adk",frameworkName:"Google Agent Development Kit",requiredEnvironment:["WORKSPAI_ADK_PROVIDER","ADK_MODEL"],stability:le("google-adk-python")},{id:"agent.google-adk.typescript",aliases:["agent.google-adk.typescript","agent.google-adk.node","google-adk-typescript","agent.google.typescript"],label:"Google Agent Development Kit \xB7 TypeScript",runtime:"node",adapterId:"google-adk-typescript",frameworkId:"google-adk",frameworkName:"Google Agent Development Kit",requiredEnvironment:["WORKSPAI_ADK_PROVIDER","ADK_MODEL"],stability:le("google-adk-typescript")}];function Ln(e){return Vr({},{trustReviewedReleaseAdmissions:true}).resolveAdapter(e.adapterId).status==="admitted"}function qn(){return $r.map(e=>structuredClone(e))}function Xl(){return qn()}function Hl(e){let t=typeof e=="string"?Br(e):e;return !!(t&&Ln(t))}function Br(e){if(!e)return null;let t=e.trim().toLowerCase(),r=$r.find(o=>o.aliases.some(a=>a.toLowerCase()===t));return r?structuredClone(r):null}function Jl(e){return Br(e)!==null}async function Zl(e){let t=new Date().toISOString(),r=h$2();await M.ensureDir(e.projectPath),await M.writeFile(x.join(e.projectPath,"README.md"),`# ${e.projectName}
4690
+ `),d(t.state,`${JSON.stringify({notice:"Generated and managed by Workspai",schemaVersion:"workspai.agent-framework-instance.v1",adapterId:J.adapter.id,frameworkVersion:Lr,runtime:"node",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],providerProfiles:["gemini-api","vertex-ai"]},null,2)}
4691
+ `)]}function Pn(e,t){let r=w(J,qr(t),t.existingFiles,t.ownershipLedger),o=P({adapter:Je,mode:e,adapterInput:t,rendered:r,dependencyRecommendation:{path:St(t.instanceName).dependencyManifest,summary:"Use the isolated agent dependency manifest; do not rewrite the repository root dependency graph."}}),a=[...o.blockers,...On(e,t)];return a.length===0?o:{...o,status:"blocked",blockers:a}}var Je={manifest:J,detect(e){return A(e,J)},plan:Pn,render(e){return w(J,qr(e),e.existingFiles,e.ownershipLedger)},context(e){let t=St(e.instanceName);return {adapterId:J.adapter.id,frameworkId:J.framework.id,runtime:"node>=20.19",entrypoint:t.entrypoint,dependencyManifest:t.dependencyManifest,requiredEnvironment:[...ae],verificationCommands:[`cd ${t.root} && npm test`],boundaries:["Workspai remains the canonical workspace and verification authority.","Google ADK owns the agent loop, tool dispatch, and in-memory session state only.","gemini-api and vertex-ai are provider profiles, not Workspai gateway kits.","Model-provider network access and mutating tools require explicit grants."]}},validate(e){return O(Je,e)},resolveRuntime(e){return S("node",">=20.19",e)}};function He(e){return `${e.platform}\0${e.runtime}\0${e.frameworkVersion}`}function Sn(e){return `${e.platform}/${e.runtime}/${e.frameworkVersion}`}function Nn(e){return e.implementation.platforms.flatMap(t=>e.implementation.runtimes.flatMap(r=>e.framework.testedVersions.map(o=>({platform:t,runtime:r,frameworkVersion:o}))))}function Nt(e){let t=[];try{a$2(e.manifest,je,"Agent framework manifest");}catch(n){t.push(n instanceof Error?n.message:String(n));}t.push(...Me(e.manifest).map(n=>`manifest: ${n}`));let r=Nn(e.manifest),o=new Map,a=new Set;for(let n of e.reports){let l=false,c={platform:n.environment.platform,runtime:n.environment.runtime,frameworkVersion:n.frameworkVersion},i=He(c),p=Sn(c);if(a.has(i)){t.push(`duplicate conformance report for ${p}`);continue}a.add(i);try{a$2(n,ur,`Agent framework conformance ${p}`);}catch(I){l=true,t.push(I instanceof Error?I.message:String(I));}for(let I of fr(n))l=true,t.push(`conformance ${p}: ${I}`);(n.adapter.id!==e.manifest.adapter.id||n.adapter.version!==e.manifest.adapter.version)&&(l=true,t.push(`conformance ${p}: adapter identity does not match the manifest`)),n.adapter.manifestSha256!==e.manifestSha256&&(l=true,t.push(`conformance ${p}: manifest digest does not match`));let _=e.implementationSha256ByPlatform?.[c.platform]??e.implementationSha256;(!_||n.adapter.implementationSha256!==_)&&(l=true,t.push(`conformance ${p}: implementation digest does not match`)),e.manifest.implementation.platforms.includes(c.platform)||(l=true,t.push(`conformance ${p}: platform is not advertised by the manifest`)),e.manifest.implementation.runtimes.includes(c.runtime)||(l=true,t.push(`conformance ${p}: runtime is not advertised by the manifest`)),e.manifest.framework.testedVersions.includes(c.frameworkVersion)||(l=true,t.push(`conformance ${p}: framework version is not in the tested baseline`)),n.verdict!=="admitted"&&(l=true,n.blockers.length===0?t.push(`conformance ${p}: report verdict is blocked`):t.push(...n.blockers.map(I=>`conformance ${p}: ${I}`))),n.verdict==="admitted"&&!l&&o.set(i,c);}for(let n of r)o.has(He(n))||t.push(`missing admitted lane: ${n.platform}/${n.runtime}/${n.frameworkVersion}`);e.manifest.adapter.stability==="experimental"&&t.push("experimental adapters are not selectable"),e.manifest.adapter.stability==="deprecated"&&t.push("deprecated adapters are not selectable");let s=[...new Set(t)].sort();return {status:s.length===0?"admitted":"blocked",requiredLanes:r,admittedLanes:[...o.values()].sort((n,l)=>He(n).localeCompare(He(l))),blockers:s}}var Wr={schemaVersion:"workspai.agent-framework-release-admissions.v2",reviewedAt:"2026-09-30T08:35:35.131Z",source:{repository:"chistiq/workspai",commit:"532a8e91831682cf679e616916a6c9fe34de4c7b",workflowRunId:36690151085,workflowUrl:"https://github.com/chistiq/workspai/actions/runs/36690151085",conclusion:"success"},adapters:[{id:"microsoft-agent-framework-python",version:"0.1.0",manifestSha256:"e2f9d02b634bcf160e893484dee26320f0325f21812d8b6ff3a87a694502dbe1",implementationSha256ByPlatform:{darwin:"ad2e50a89a34ecf83de867295e938434988ac964552c1f4b8c5714e170f49994",linux:"ad2e50a89a34ecf83de867295e938434988ac964552c1f4b8c5714e170f49994",win32:"a1bc5c4e28f20ed93e815bba50bc05f4ba0e52b86ee7e17baaf9bd9e722e460e"},frameworkVersion:"1.19.0",runtime:"python",platforms:["darwin","linux","win32"]},{id:"microsoft-agent-framework-dotnet",version:"0.1.0",manifestSha256:"8b40a5876fc105e4cc3d55a93f5628937f113ad8ecdb642f9b6dda553b556d2e",implementationSha256ByPlatform:{darwin:"44fd6f71791c7f257b2717c21f39087385a4a1b8361a3047799b47d2a21a6c3f",linux:"44fd6f71791c7f257b2717c21f39087385a4a1b8361a3047799b47d2a21a6c3f",win32:"44fd6f71791c7f257b2717c21f39087385a4a1b8361a3047799b47d2a21a6c3f"},frameworkVersion:"1.22.0",runtime:"dotnet",platforms:["darwin","linux","win32"]},{id:"openai-agents-python",version:"0.1.0",manifestSha256:"9c1d3bb75d3273285c4f7f4f609a795f8fe04cad3ff1c7fe6e5b7eeff7acaca1",implementationSha256ByPlatform:{darwin:"40173b86c8bfd87bc6372585647c9374d87b70e77d9521880142a81a987305ba",linux:"40173b86c8bfd87bc6372585647c9374d87b70e77d9521880142a81a987305ba",win32:"c56c03b8b64eb60ce14d12fc6e3fb28539e75e25812488db436fc1e498ecdd1a"},frameworkVersion:"0.22.3",runtime:"python",platforms:["darwin","linux","win32"]},{id:"openai-agents-typescript",version:"0.1.0",manifestSha256:"35b4e04cda42e227ea8d352e3c87ec1ed7c5f53b39aafb734e895d456a32c38f",implementationSha256ByPlatform:{darwin:"df11882aa104230b308c838e4c0f0ea399aa4383f6b51a37e15bd978e8229e90",linux:"df11882aa104230b308c838e4c0f0ea399aa4383f6b51a37e15bd978e8229e90",win32:"df11882aa104230b308c838e4c0f0ea399aa4383f6b51a37e15bd978e8229e90"},frameworkVersion:"0.18.0",runtime:"node",platforms:["darwin","linux","win32"]},{id:"google-adk-python",version:"0.1.0",manifestSha256:"93cf8d3f654b8dd0256874ace35ade182c409025ca5100aabbb67a3ab57c77ea",implementationSha256ByPlatform:{darwin:"f79a22ff2bb150e3e2b4669dad1923bd2abf62902f99f39abd604256cc8d7eca",linux:"f79a22ff2bb150e3e2b4669dad1923bd2abf62902f99f39abd604256cc8d7eca",win32:"a2b5d5d72df66a2f9fc00d5a833e37b567d5310d11c656e1c07068497202a793"},frameworkVersion:"2.10.0",runtime:"python",platforms:["darwin","linux","win32"]},{id:"google-adk-typescript",version:"0.1.0",manifestSha256:"be9927f4a04a28632b7396b7fa445c1e3b0da4057179e2665c229ab07332a57d",implementationSha256ByPlatform:{darwin:"8665efbe36433a01da927d655aa92ed4c9de9d5dc567e68852808b71885eb2db",linux:"8665efbe36433a01da927d655aa92ed4c9de9d5dc567e68852808b71885eb2db",win32:"8665efbe36433a01da927d655aa92ed4c9de9d5dc567e68852808b71885eb2db"},frameworkVersion:"2.1.0",runtime:"node",platforms:["darwin","linux","win32"]}]};function It(e){return Array.isArray(e)?e.map(t=>It(t)):e&&typeof e=="object"?Object.fromEntries(Object.entries(e).sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>[t,It(r)])):e}function Ze(e){return createHash("sha256").update(`${JSON.stringify(e.manifest)}
4692
+ `).digest("hex")}function Ur(e){let t={projectRoot:"/workspai/qualification/project",instanceName:"qualification-probe",target:{project:"qualification-project",artifactPrefix:"."}},r={algorithm:"workspai.agent-framework-implementation.semantic.v1",adapterId:e.manifest.adapter.id,render:e.render(t),plans:{scaffold:e.plan("scaffold",t),attach:e.plan("attach",t)},context:e.context(t),validation:e.validate(t),runtimeResolution:{unavailable:e.resolveRuntime([]),resolved:e.resolveRuntime([e.manifest.implementation.runtimes[0]]),ambiguous:e.resolveRuntime(e.manifest.implementation.runtimes.map(o=>`${o}@qualification`))}};return createHash("sha256").update(JSON.stringify(It(r))).digest("hex")}var Cn="workspai.agent-framework-release-admissions.v2",ye=["linux","darwin","win32"];function Qe(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function jn(e){if(!Qe(e)||e.schemaVersion!==Cn)throw new Error("Bundled agent framework release admission schema is incompatible.");if(!Qe(e.source))throw new Error("Bundled agent framework release admission source is missing.");if(!/^[a-f0-9]{40}$/.test(String(e.source.commit))||!Number.isSafeInteger(e.source.workflowRunId)||Number(e.source.workflowRunId)<=0||e.source.workflowUrl!==`https://github.com/chistiq/workspai/actions/runs/${String(e.source.workflowRunId)}`||e.source.conclusion!=="success")throw new Error("Bundled agent framework release admission source is invalid.");if(!Array.isArray(e.adapters))throw new Error("Bundled agent framework release admission inventory is invalid.");let t=new Set;return e.adapters.map((r,o)=>{if(!Qe(r))throw new Error(`Bundled agent framework release admission ${o} is invalid.`);let a=String(r.id??""),s=String(r.version??""),n=String(r.manifestSha256??""),l=Qe(r.implementationSha256ByPlatform)?r.implementationSha256ByPlatform:{},c=String(r.frameworkVersion??""),i=String(r.runtime??"");if(!/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/.test(a)||!s||!/^[a-f0-9]{64}$/.test(n)||ye.some(_=>!/^[a-f0-9]{64}$/.test(String(l[_]??"")))||!c||!i)throw new Error(`Bundled agent framework release admission ${a||o} is malformed.`);if(t.has(a))throw new Error(`Duplicate bundled agent framework admission: ${a}`);t.add(a);let p=Array.isArray(r.platforms)?r.platforms:[];if(p.length!==ye.length||ye.some(_=>!p.includes(_)))throw new Error(`Bundled agent framework admission has incomplete platforms: ${a}`);return {id:a,version:s,manifestSha256:n,implementationSha256ByPlatform:Object.fromEntries(ye.map(_=>[_,String(l[_])])),frameworkVersion:c,runtime:i,platforms:[...ye]}})}var Mn=Object.freeze(jn(Wr));function Ct(e){let t=Mn.find(s=>s.id===e.manifest.adapter.id);if(!t)return {status:"blocked",admission:null,blockers:[`No reviewed release admission exists for ${e.manifest.adapter.id}.`]};let r=[];t.version!==e.manifest.adapter.version&&r.push("adapter version changed after release admission"),t.manifestSha256!==Ze(e)&&r.push("adapter manifest changed after release admission"),e.manifest.framework.testedVersions.includes(t.frameworkVersion)||r.push("framework baseline changed after release admission"),e.manifest.implementation.runtimes.includes(t.runtime)||r.push("runtime changed after release admission");let o=[...e.manifest.implementation.platforms].sort(),a=[...t.platforms].sort();return JSON.stringify(o)!==JSON.stringify(a)&&r.push("platform matrix changed after release admission"),{status:r.length===0?"admitted":"blocked",admission:structuredClone(t),blockers:r}}function se(e){return e.trim().toLowerCase().replace(/[^a-z0-9.-]+/g,"-")}function W(e){return structuredClone({manifest:e.manifest,manifestSha256:e.manifestSha256,implementationSha256:e.implementationSha256,source:e.source,conformanceReports:e.conformanceReports})}function Gn(e){return {...W(e),...e.releaseAdapter?{releaseAdapter:e.releaseAdapter}:{}}}var et=class{#e=new Map;register(t){a$2(t.manifest,je,`Agent framework adapter ${t.manifest.adapter?.id??"unknown"}`);let r=Me(t.manifest);if(r.length>0)throw new Error(`Cannot register agent framework adapter ${t.manifest.adapter.id}: ${r.join("; ")}`);if(!/^[a-f0-9]{64}$/.test(t.manifestSha256))throw new Error("Agent framework manifest digest must be a lowercase SHA-256 value.");if(!/^[a-f0-9]{64}$/.test(t.implementationSha256))throw new Error("Agent framework implementation digest must be a lowercase SHA-256 value.");let o=se(t.manifest.adapter.id);if(this.#e.has(o))throw new Error(`Agent framework adapter id is already registered: ${o}`);return this.#e.set(o,Gn(t)),this}get(t){let r=this.#e.get(se(t));return r?W(r):null}list(){return [...this.#e.values()].map(W).sort((t,r)=>t.manifest.adapter.id.localeCompare(r.manifest.adapter.id))}resolveAdapter(t){let r=this.#e.get(se(t));if(!r)return {status:"unresolved",entry:null,blockers:[]};let o=r.releaseAdapter?Ct(r.releaseAdapter):null;if(o?.status==="admitted")return {status:"admitted",entry:W(r),blockers:[]};let a=Nt({manifest:r.manifest,manifestSha256:r.manifestSha256,implementationSha256:r.implementationSha256,reports:r.conformanceReports});return a.status==="admitted"?{status:"admitted",entry:W(r),blockers:[]}:{status:"blocked",entry:W(r),blockers:[...o?.blockers??[],...a.blockers]}}async resolveProject(t){let r=t.runtime?se(t.runtime):void 0,o=[...this.#e.values()].filter(i=>!r||i.manifest.implementation.runtimes.some(p=>se(p)===r)),a=await Promise.all(o.map(i=>A(t.projectRoot,i.manifest))),s=a.filter(i=>i.detected);if(s.length===0)return {status:"unresolved",entry:null,candidates:a,blockers:[]};if(s.length>1)return {status:"conflict",entry:null,candidates:a,blockers:[`Multiple agent framework adapters matched authored evidence: ${s.map(i=>i.adapterId).sort().join(", ")}`]};let n=this.#e.get(se(s[0].adapterId));if(!n)throw new Error("Agent framework registry index is inconsistent.");let l=n.releaseAdapter?Ct(n.releaseAdapter):null,c=Nt({manifest:n.manifest,manifestSha256:n.manifestSha256,implementationSha256:n.implementationSha256,reports:n.conformanceReports});return c.status!=="admitted"&&l?.status!=="admitted"?{status:"blocked",entry:W(n),candidates:a,blockers:[...l?.blockers??[],...c.blockers]}:{status:"matched",entry:W(n),candidates:a,blockers:[]}}};var jt=Object.freeze([Ke,qe,Ve,$e,Xe,Je]);function Dn(e){return Ze(e)}function Fn(e){return Ur(e)}function Vr(e={},t={}){let r=new et;for(let o of jt)r.register({manifest:o.manifest,manifestSha256:Dn(o),implementationSha256:Fn(o),source:"builtin",conformanceReports:structuredClone(e[o.manifest.adapter.id]??[]),...t.trustReviewedReleaseAdmissions?{releaseAdapter:o}:{}});return r}function le(e){return jt.find(r=>r.manifest.adapter.id===e)?.manifest.adapter.stability==="stable"?"stable":"preview"}var $r=[{id:"agent.microsoft.python",aliases:["agent.microsoft.python","microsoft-agent-python","agent-framework-python"],label:"Microsoft Agent Framework \xB7 Python",runtime:"python",adapterId:"microsoft-agent-framework-python",frameworkId:"microsoft-agent-framework",frameworkName:"Microsoft Agent Framework",requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"],stability:le("microsoft-agent-framework-python")},{id:"agent.microsoft.dotnet",aliases:["agent.microsoft.dotnet","microsoft-agent-dotnet","agent-framework-dotnet"],label:"Microsoft Agent Framework \xB7 .NET",runtime:"dotnet",adapterId:"microsoft-agent-framework-dotnet",frameworkId:"microsoft-agent-framework",frameworkName:"Microsoft Agent Framework",requiredEnvironment:["FOUNDRY_PROJECT_ENDPOINT","FOUNDRY_MODEL"],stability:le("microsoft-agent-framework-dotnet")},{id:"agent.openai.python",aliases:["agent.openai.python","openai-agents-python","openai-agent-python"],label:"OpenAI Agents SDK \xB7 Python",runtime:"python",adapterId:"openai-agents-python",frameworkId:"openai-agents",frameworkName:"OpenAI Agents SDK",requiredEnvironment:["OPENAI_API_KEY","OPENAI_MODEL"],stability:le("openai-agents-python")},{id:"agent.openai.typescript",aliases:["agent.openai.typescript","agent.openai.node","openai-agents-typescript","openai-agent-typescript"],label:"OpenAI Agents SDK \xB7 TypeScript",runtime:"node",adapterId:"openai-agents-typescript",frameworkId:"openai-agents",frameworkName:"OpenAI Agents SDK",requiredEnvironment:["OPENAI_API_KEY","OPENAI_MODEL"],stability:le("openai-agents-typescript")},{id:"agent.google-adk.python",aliases:["agent.google-adk.python","google-adk-python","agent.google.python"],label:"Google Agent Development Kit \xB7 Python",runtime:"python",adapterId:"google-adk-python",frameworkId:"google-adk",frameworkName:"Google Agent Development Kit",requiredEnvironment:["WORKSPAI_ADK_PROVIDER","ADK_MODEL"],stability:le("google-adk-python")},{id:"agent.google-adk.typescript",aliases:["agent.google-adk.typescript","agent.google-adk.node","google-adk-typescript","agent.google.typescript"],label:"Google Agent Development Kit \xB7 TypeScript",runtime:"node",adapterId:"google-adk-typescript",frameworkId:"google-adk",frameworkName:"Google Agent Development Kit",requiredEnvironment:["WORKSPAI_ADK_PROVIDER","ADK_MODEL"],stability:le("google-adk-typescript")}];function Ln(e){return Vr({},{trustReviewedReleaseAdmissions:true}).resolveAdapter(e.adapterId).status==="admitted"}function qn(){return $r.map(e=>structuredClone(e))}function Xl(){return qn()}function Jl(e){let t=typeof e=="string"?Br(e):e;return !!(t&&Ln(t))}function Br(e){if(!e)return null;let t=e.trim().toLowerCase(),r=$r.find(o=>o.aliases.some(a=>a.toLowerCase()===t));return r?structuredClone(r):null}function Hl(e){return Br(e)!==null}async function Zl(e){let t=new Date().toISOString(),r=h$2();await M.ensureDir(e.projectPath),await M.writeFile(x.join(e.projectPath,"README.md"),`# ${e.projectName}
4684
4693
 
4685
4694
  This agent project is governed by Workspai and uses ${e.kit.label}.
4686
4695
 
@@ -4699,7 +4708,7 @@ node_modules/
4699
4708
  dist/
4700
4709
  bin/
4701
4710
  obj/
4702
- `,{encoding:"utf8",flag:"wx"}),await M.ensureDir(x.join(e.projectPath,".workspai")),await M.writeJson(x.join(e.projectPath,".workspai","context.json"),{engine:"npm",runtime:e.kit.runtime,framework:e.kit.frameworkId,kind:"agent",category:"agent",kit:e.kit.id},{spaces:2}),await M.writeJson(x.join(e.projectPath,".workspai","project.json"),{schema_version:"1.0",name:e.projectName,slug:e.projectName,kind:"agent",project_type:"agent",category:"agent",runtime:e.kit.runtime,framework:e.kit.frameworkId,framework_display_name:e.kit.frameworkName,kit_name:e.kit.id,kit:e.kit.id,engine:"npm",support_tier:"extended",module_support:false,modules:[],workspai_version:r,rapidkit_version:r,generated_by:"workspai",generated_at:t,contracts:{owns:[],apis:[],publishes:[],consumes:[],dependsOn:[],env:[...e.kit.requiredEnvironment]}},{spaces:2});}var tt="gateway",Mt="gateway";function zr(e){return e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_.]+|[-_.]+$/g,"").slice(0,64)||"openrouter-gateway"}function Yr(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"openrouter-gateway"}var Gt={schemaVersion:"workspai.model-gateway-version-baselines.v1",reviewedAt:"2026-09-21",baselines:[{adapterId:"openrouter-typescript",gatewayId:"openrouter",gatewayName:"OpenRouter",runtime:"node",policy:"tested-baseline",releaseChannel:"stable",sdkVersion:"1.3.11",discoveryDate:"2026-09-21",declaredMaturity:"stable-1.x",runtimeRequirement:"Node.js 18, 20, or 22 LTS as documented by the SDK RUNTIMES.md; generated projects require Node.js >=20",upstreamReleaseUrl:"https://github.com/OpenRouterTeam/typescript-sdk/releases/tag/v1.3.11",registryUrl:"https://registry.npmjs.org/@openrouter/sdk/1.3.11",notes:"On 2026-09-21 GitHub released v1.3.11 (prerelease=false). Later the same day npm latest became 1.3.11 and registry.npmjs.org/@openrouter/sdk/1.3.11 returned 200. The pin is the latest non-prerelease version present on both the GitHub release tag and the npm registry.",packages:[{ecosystem:"npm",name:"@openrouter/sdk",version:"1.3.11",channel:"stable",role:"sdk-core",registryUrl:"https://registry.npmjs.org/@openrouter/sdk/1.3.11"},{ecosystem:"npm",name:"typescript",version:"5.9.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/typescript/5.9.3"},{ecosystem:"npm",name:"@types/node",version:"22.20.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/@types/node/22.20.3"}],automaticUpgrade:false},{adapterId:"openrouter-python",gatewayId:"openrouter",gatewayName:"OpenRouter",runtime:"python",policy:"tested-baseline",releaseChannel:"stable",sdkVersion:"1.2.11",discoveryDate:"2026-09-21",declaredMaturity:"stable-as-of-v1.0",runtimeRequirement:"Python >=3.10 as declared by the PyPI package requires_python field",upstreamReleaseUrl:"https://github.com/OpenRouterTeam/python-sdk/releases/tag/v1.2.11",registryUrl:"https://pypi.org/pypi/openrouter/1.2.11",notes:"PyPI classifiers are empty. Maturity is taken from the official package description: The OpenRouter Python SDK is stable as of v1.0. GitHub tag v1.2.11 matches PyPI 1.2.11.",packages:[{ecosystem:"pypi",name:"openrouter",version:"1.2.11",channel:"stable",role:"sdk-core",registryUrl:"https://pypi.org/pypi/openrouter/1.2.11"}],automaticUpgrade:false}],excluded:[{language:"go",module:"github.com/OpenRouterTeam/go-sdk",discoveryDate:"2026-09-21",latestObservedVersion:"v0.8.11",declaredMaturity:"beta",reason:"The official Go SDK README classifies the current 0.x release line as beta and warns that breaking changes may ship in minor releases. Workspai does not admit beta Client SDKs to the stable gateway surface.",evidence:["https://github.com/OpenRouterTeam/go-sdk README note: This SDK is in beta. Pin to a specific version to avoid unexpected breaking changes.","https://proxy.golang.org/github.com/openrouterteam/go-sdk/@latest returned Version v0.8.11 on 2026-09-21."]}]};var Xr=/^\d+(?:\.\d+)+$/,Hr=/^\d{4}-\d{2}-\d{2}$/;function Un(e){if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Model gateway version baseline document must be an object.");let t=e;if(!Hr.test(t.reviewedAt))throw new Error("Model gateway version baseline document is missing reviewedAt.");if(!Array.isArray(t.baselines)||t.baselines.length===0)throw new Error("Model gateway version baseline document contains no adapters.");let r=new Set;for(let o of t.baselines){if(!o||typeof o.adapterId!="string"||typeof o.gatewayId!="string"||typeof o.gatewayName!="string"||o.runtime!=="node"&&o.runtime!=="python"||o.policy!=="tested-baseline"||o.releaseChannel!=="stable"||!Xr.test(o.sdkVersion)||!Hr.test(o.discoveryDate)||typeof o.declaredMaturity!="string"||/beta|alpha|preview|rc|canary|next|dev/i.test(o.declaredMaturity)||typeof o.runtimeRequirement!="string"||typeof o.upstreamReleaseUrl!="string"||!o.upstreamReleaseUrl.startsWith("https://")||typeof o.registryUrl!="string"||!o.registryUrl.startsWith("https://")||o.automaticUpgrade!==false||!Array.isArray(o.packages)||o.packages.length===0)throw new Error(`Invalid model gateway version baseline: ${String(o?.adapterId)}`);if(r.has(o.adapterId))throw new Error(`Duplicate model gateway version baseline: ${o.adapterId}`);r.add(o.adapterId);let a=new Set,s=null;for(let n of o.packages){if(!n||n.ecosystem!=="npm"&&n.ecosystem!=="pypi"||typeof n.name!="string"||!Xr.test(n.version)||n.channel!=="stable"||n.role!=="sdk-core"&&n.role!=="runtime-support"||typeof n.registryUrl!="string"||!n.registryUrl.startsWith("https://"))throw new Error(`Invalid package baseline in ${o.adapterId}.`);if(a.has(n.name))throw new Error(`Duplicate package ${n.name} in ${o.adapterId}.`);a.add(n.name),n.role==="sdk-core"&&(s=n.version);}if(!s||s!==o.sdkVersion)throw new Error(`SDK version is not bound to the core package in ${o.adapterId}.`)}if(!Array.isArray(t.excluded))throw new Error("Model gateway version baseline document must declare excluded languages.")}Un(Gt);var Dt=Gt,Vn=Object.freeze(structuredClone(Dt.baselines).map(e=>Object.freeze({...e,packages:Object.freeze(e.packages.map(t=>Object.freeze(t)))})));Object.freeze(structuredClone(Dt.excluded).map(e=>Object.freeze({...e})));function rt(e){return Vn.find(t=>t.adapterId===e)??null}function ce(e,t){let r=e.packages.find(o=>o.name===t);if(!r)throw new Error(`Package ${t} is not in baseline ${e.adapterId}.`);return r.version}var de="gateway.policy.json";var we=["price","throughput","latency","exacto"],ke=["model","none"],ot=["p50","p75","p90","p99"],nt=["prompt","completion","request","image","audio"],Ee=["int4","int8","fp4","mxfp4","nvfp4","fp6","fp8","mxfp8","fp16","bf16","fp32","unknown"],at=["allow","deny"],st=`# Credentials and local secrets
4711
+ `,{encoding:"utf8",flag:"wx"}),await M.ensureDir(x.join(e.projectPath,".workspai")),await M.writeJson(x.join(e.projectPath,".workspai","context.json"),{engine:"npm",runtime:e.kit.runtime,framework:e.kit.frameworkId,kind:"agent",category:"agent",kit:e.kit.id},{spaces:2}),await M.writeJson(x.join(e.projectPath,".workspai","project.json"),{schema_version:"1.0",name:e.projectName,slug:e.projectName,kind:"agent",project_type:"agent",category:"agent",runtime:e.kit.runtime,framework:e.kit.frameworkId,framework_display_name:e.kit.frameworkName,kit_name:e.kit.id,kit:e.kit.id,engine:"npm",support_tier:"extended",module_support:false,modules:[],workspai_version:r,rapidkit_version:r,generated_by:"workspai",generated_at:t,contracts:{owns:[],apis:[],publishes:[],consumes:[],dependsOn:[],env:[...e.kit.requiredEnvironment]}},{spaces:2});}var tt="gateway",Mt="gateway";function zr(e){return e.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_.]+|[-_.]+$/g,"").slice(0,64)||"openrouter-gateway"}function Yr(e){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"openrouter-gateway"}var Gt={schemaVersion:"workspai.model-gateway-version-baselines.v1",reviewedAt:"2026-09-28",baselines:[{adapterId:"openrouter-typescript",gatewayId:"openrouter",gatewayName:"OpenRouter",runtime:"node",policy:"tested-baseline",releaseChannel:"stable",sdkVersion:"1.3.33",discoveryDate:"2026-09-28",declaredMaturity:"stable-1.x",runtimeRequirement:"Node.js 18, 20, or 22 LTS as documented by the SDK RUNTIMES.md; generated projects require Node.js >=20",upstreamReleaseUrl:"https://github.com/OpenRouterTeam/typescript-sdk/releases/tag/v1.3.33",registryUrl:"https://registry.npmjs.org/@openrouter/sdk/1.3.33",notes:"Proposed 2026-09-28. Qualifies only after Linux, macOS, and Windows generated-project and lifecycle evidence on the same commit.",packages:[{ecosystem:"npm",name:"@openrouter/sdk",version:"1.3.33",channel:"stable",role:"sdk-core",registryUrl:"https://registry.npmjs.org/@openrouter/sdk/1.3.33"},{ecosystem:"npm",name:"typescript",version:"5.9.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/typescript/5.9.3"},{ecosystem:"npm",name:"@types/node",version:"22.20.3",channel:"stable",role:"runtime-support",registryUrl:"https://registry.npmjs.org/@types/node/22.20.3"}],automaticUpgrade:false},{adapterId:"openrouter-python",gatewayId:"openrouter",gatewayName:"OpenRouter",runtime:"python",policy:"tested-baseline",releaseChannel:"stable",sdkVersion:"1.2.32",discoveryDate:"2026-09-28",declaredMaturity:"stable-as-of-v1.0",runtimeRequirement:"Python >=3.10 as declared by the PyPI package requires_python field",upstreamReleaseUrl:"https://github.com/OpenRouterTeam/python-sdk/releases/tag/v1.2.32",registryUrl:"https://pypi.org/pypi/openrouter/1.2.32",notes:"Proposed 2026-09-28. Qualifies only after Linux, macOS, and Windows generated-project and lifecycle evidence on the same commit.",packages:[{ecosystem:"pypi",name:"openrouter",version:"1.2.32",channel:"stable",role:"sdk-core",registryUrl:"https://pypi.org/pypi/openrouter/1.2.32"}],automaticUpgrade:false}],excluded:[{language:"go",module:"github.com/OpenRouterTeam/go-sdk",discoveryDate:"2026-09-28",latestObservedVersion:"v0.8.32",declaredMaturity:"beta",reason:"The official Go SDK README classifies the current 0.x release line as beta and warns that breaking changes may ship in minor releases. Workspai does not admit beta Client SDKs to the stable gateway surface.",evidence:["https://github.com/OpenRouterTeam/go-sdk README note: This SDK is in beta. Pin to a specific version to avoid unexpected breaking changes.","https://proxy.golang.org/github.com/openrouterteam/go-sdk/@latest returned Version v0.8.32 on 2026-09-28."]}]};var Xr=/^\d+(?:\.\d+)+$/,Jr=/^\d{4}-\d{2}-\d{2}$/;function Un(e){if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Model gateway version baseline document must be an object.");let t=e;if(!Jr.test(t.reviewedAt))throw new Error("Model gateway version baseline document is missing reviewedAt.");if(!Array.isArray(t.baselines)||t.baselines.length===0)throw new Error("Model gateway version baseline document contains no adapters.");let r=new Set;for(let o of t.baselines){if(!o||typeof o.adapterId!="string"||typeof o.gatewayId!="string"||typeof o.gatewayName!="string"||o.runtime!=="node"&&o.runtime!=="python"||o.policy!=="tested-baseline"||o.releaseChannel!=="stable"||!Xr.test(o.sdkVersion)||!Jr.test(o.discoveryDate)||typeof o.declaredMaturity!="string"||/beta|alpha|preview|rc|canary|next|dev/i.test(o.declaredMaturity)||typeof o.runtimeRequirement!="string"||typeof o.upstreamReleaseUrl!="string"||!o.upstreamReleaseUrl.startsWith("https://")||typeof o.registryUrl!="string"||!o.registryUrl.startsWith("https://")||o.automaticUpgrade!==false||!Array.isArray(o.packages)||o.packages.length===0)throw new Error(`Invalid model gateway version baseline: ${String(o?.adapterId)}`);if(r.has(o.adapterId))throw new Error(`Duplicate model gateway version baseline: ${o.adapterId}`);r.add(o.adapterId);let a=new Set,s=null;for(let n of o.packages){if(!n||n.ecosystem!=="npm"&&n.ecosystem!=="pypi"||typeof n.name!="string"||!Xr.test(n.version)||n.channel!=="stable"||n.role!=="sdk-core"&&n.role!=="runtime-support"||typeof n.registryUrl!="string"||!n.registryUrl.startsWith("https://"))throw new Error(`Invalid package baseline in ${o.adapterId}.`);if(a.has(n.name))throw new Error(`Duplicate package ${n.name} in ${o.adapterId}.`);a.add(n.name),n.role==="sdk-core"&&(s=n.version);}if(!s||s!==o.sdkVersion)throw new Error(`SDK version is not bound to the core package in ${o.adapterId}.`)}if(!Array.isArray(t.excluded))throw new Error("Model gateway version baseline document must declare excluded languages.")}Un(Gt);var Dt=Gt,Vn=Object.freeze(structuredClone(Dt.baselines).map(e=>Object.freeze({...e,packages:Object.freeze(e.packages.map(t=>Object.freeze(t)))})));Object.freeze(structuredClone(Dt.excluded).map(e=>Object.freeze({...e})));function rt(e){return Vn.find(t=>t.adapterId===e)??null}function ce(e,t){let r=e.packages.find(o=>o.name===t);if(!r)throw new Error(`Package ${t} is not in baseline ${e.adapterId}.`);return r.version}var de="gateway.policy.json";var we=["price","throughput","latency","exacto"],ke=["model","none"],ot=["p50","p75","p90","p99"],nt=["prompt","completion","request","image","audio"],Ee=["int4","int8","fp4","mxfp4","nvfp4","fp6","fp8","mxfp8","fp16","bf16","fp32","unknown"],at=["allow","deny"],st=`# Credentials and local secrets
4703
4712
  .env
4704
4713
  .env.*
4705
4714
  !.env.example
@@ -4725,13 +4734,13 @@ coverage/
4725
4734
  # Editor and OS
4726
4735
  .DS_Store
4727
4736
  Thumbs.db
4728
- `;var Jr="9".repeat(400);function g(e,t,r,o,a){return r?{id:e,decision:t,needle:r,typescript:o,python:a}:{id:e,decision:t,typescript:o,python:a}}var it=[g("unknown-root-field","reject","unknown field",{widget:true},{widget:true}),g("unknown-provider-field","reject","unknown field",{provider:{fallbacks:true}},{provider:{fallbacks:true}}),g("unknown-max-price-key","reject","unknown field",{provider:{maxPrice:{tokens:"1"}}},{provider:{max_price:{tokens:"1"}}}),g("unknown-percentile-key","reject","unknown field",{provider:{preferredMaxLatency:{p95:1}}},{provider:{preferred_max_latency:{p95:1}}}),g("unknown-sort-field","reject","unknown field",{provider:{sort:{by:"price",extra:true}}},{provider:{sort:{by:"price",extra:true}}}),g("models-not-array","reject","models must be an array",{models:"openai/gpt"},{models:"openai/gpt"}),g("allow-fallbacks-string","reject","boolean",{provider:{allowFallbacks:"yes"}},{provider:{allow_fallbacks:"yes"}}),g("require-parameters-number","reject","boolean",{provider:{requireParameters:1}},{provider:{require_parameters:1}}),g("zdr-number","reject","boolean",{provider:{zdr:1}},{provider:{zdr:1}}),g("empty-model-name","reject","non-empty string",{models:[" "]},{models:[" "]}),g("whitespace-provider-name","reject","non-empty string",{provider:{order:[" "]}},{provider:{order:[" "]}}),g("duplicate-models","reject","duplicates",{models:["a","a"]},{models:["a","a"]}),g("duplicate-order","reject","duplicates",{provider:{order:["openai","openai"]}},{provider:{order:["openai","openai"]}}),g("only-ignore-overlap","reject","overlap",{provider:{only:["openai"],ignore:["openai"]}},{provider:{only:["openai"],ignore:["openai"]}}),g("ignored-provider-in-order","reject","ignored providers",{provider:{order:["openai"],ignore:["openai"]}},{provider:{order:["openai"],ignore:["openai"]}}),g("unsupported-quantization","reject","quantizations",{provider:{quantizations:["int5"]}},{provider:{quantizations:["int5"]}}),g("invalid-sort","reject","sort",{provider:{sort:"cheap"}},{provider:{sort:"cheap"}}),g("invalid-sort-partition","reject","partition",{provider:{sort:{by:"price",partition:"all"}}},{provider:{sort:{by:"price",partition:"all"}}}),g("zdr-with-data-collection-allow","reject","zdr",{provider:{zdr:true,dataCollection:"allow"}},{provider:{zdr:true,data_collection:"allow"}}),g("timeout-zero","reject","positive integer",{timeoutMs:0},{timeout_ms:0}),g("timeout-over-max","reject","positive integer",{timeoutMs:600001},{timeout_ms:600001}),g("max-price-number","reject","numeric string",{provider:{maxPrice:{prompt:1}}},{provider:{max_price:{prompt:1}}}),g("max-price-negative","reject","numeric string",{provider:{maxPrice:{prompt:"-1"}}},{provider:{max_price:{prompt:"-1"}}}),g("max-price-malformed","reject","numeric string",{provider:{maxPrice:{prompt:"not-a-price"}}},{provider:{max_price:{prompt:"not-a-price"}}}),g("max-price-overflow","reject","numeric string",{provider:{maxPrice:{prompt:Jr}}},{provider:{max_price:{prompt:Jr}}}),g("percentile-negative","reject","non-negative",{provider:{preferredMinThroughput:{p50:-1}}},{provider:{preferred_min_throughput:{p50:-1}}}),g("valid-minimal-policy","accept",void 0,{},{}),g("timeout-one","accept",void 0,{timeoutMs:1},{timeout_ms:1}),g("timeout-max","accept",void 0,{timeoutMs:6e5},{timeout_ms:6e5}),g("max-price-zero","accept",void 0,{provider:{maxPrice:{prompt:"0"}}},{provider:{max_price:{prompt:"0"}}}),g("max-price-micro","accept",void 0,{provider:{maxPrice:{prompt:"0.000001"}}},{provider:{max_price:{prompt:"0.000001"}}}),g("max-price-positive","accept",void 0,{provider:{maxPrice:{completion:"1.5"}}},{provider:{max_price:{completion:"1.5"}}}),g("valid-complete-policy","accept",void 0,{models:["openrouter/fallback"],timeoutMs:1,httpReferer:"https://example.test",appTitle:"gateway",provider:{allowFallbacks:false,requireParameters:true,zdr:true,dataCollection:"deny",enforceDistillableText:true,only:["anthropic"],ignore:["openai"],order:["anthropic"],sort:{by:"exacto",partition:"none"},quantizations:["fp16"],maxPrice:{prompt:"0",completion:"1.5",request:"2",image:"3",audio:"4"},preferredMinThroughput:{p50:0,p75:1,p90:2,p99:3},preferredMaxLatency:1.5}},{models:["openrouter/fallback"],timeout_ms:1,http_referer:"https://example.test",x_open_router_title:"gateway",provider:{allow_fallbacks:false,require_parameters:true,zdr:true,data_collection:"deny",enforce_distillable_text:true,only:["anthropic"],ignore:["openai"],order:["anthropic"],sort:{by:"exacto",partition:"none"},quantizations:["fp16"],max_price:{prompt:"0",completion:"1.5",request:"2",image:"3",audio:"4"},preferred_min_throughput:{p50:0,p75:1,p90:2,p99:3},preferred_max_latency:1.5}})];var pe=Object.freeze({chat:true,stream:true,tools:false,agents:false,embeddings:false,images:false,audio:false,structuredOutput:false,attach:false}),lt=["init","test","build","start"];function h(e){return `${e.join(`
4737
+ `;var Hr="9".repeat(400);function g(e,t,r,o,a){return r?{id:e,decision:t,needle:r,typescript:o,python:a}:{id:e,decision:t,typescript:o,python:a}}var it=[g("unknown-root-field","reject","unknown field",{widget:true},{widget:true}),g("unknown-provider-field","reject","unknown field",{provider:{fallbacks:true}},{provider:{fallbacks:true}}),g("unknown-max-price-key","reject","unknown field",{provider:{maxPrice:{tokens:"1"}}},{provider:{max_price:{tokens:"1"}}}),g("unknown-percentile-key","reject","unknown field",{provider:{preferredMaxLatency:{p95:1}}},{provider:{preferred_max_latency:{p95:1}}}),g("unknown-sort-field","reject","unknown field",{provider:{sort:{by:"price",extra:true}}},{provider:{sort:{by:"price",extra:true}}}),g("models-not-array","reject","models must be an array",{models:"openai/gpt"},{models:"openai/gpt"}),g("allow-fallbacks-string","reject","boolean",{provider:{allowFallbacks:"yes"}},{provider:{allow_fallbacks:"yes"}}),g("require-parameters-number","reject","boolean",{provider:{requireParameters:1}},{provider:{require_parameters:1}}),g("zdr-number","reject","boolean",{provider:{zdr:1}},{provider:{zdr:1}}),g("empty-model-name","reject","non-empty string",{models:[" "]},{models:[" "]}),g("whitespace-provider-name","reject","non-empty string",{provider:{order:[" "]}},{provider:{order:[" "]}}),g("duplicate-models","reject","duplicates",{models:["a","a"]},{models:["a","a"]}),g("duplicate-order","reject","duplicates",{provider:{order:["openai","openai"]}},{provider:{order:["openai","openai"]}}),g("only-ignore-overlap","reject","overlap",{provider:{only:["openai"],ignore:["openai"]}},{provider:{only:["openai"],ignore:["openai"]}}),g("ignored-provider-in-order","reject","ignored providers",{provider:{order:["openai"],ignore:["openai"]}},{provider:{order:["openai"],ignore:["openai"]}}),g("unsupported-quantization","reject","quantizations",{provider:{quantizations:["int5"]}},{provider:{quantizations:["int5"]}}),g("invalid-sort","reject","sort",{provider:{sort:"cheap"}},{provider:{sort:"cheap"}}),g("invalid-sort-partition","reject","partition",{provider:{sort:{by:"price",partition:"all"}}},{provider:{sort:{by:"price",partition:"all"}}}),g("zdr-with-data-collection-allow","reject","zdr",{provider:{zdr:true,dataCollection:"allow"}},{provider:{zdr:true,data_collection:"allow"}}),g("timeout-zero","reject","positive integer",{timeoutMs:0},{timeout_ms:0}),g("timeout-over-max","reject","positive integer",{timeoutMs:600001},{timeout_ms:600001}),g("max-price-number","reject","numeric string",{provider:{maxPrice:{prompt:1}}},{provider:{max_price:{prompt:1}}}),g("max-price-negative","reject","numeric string",{provider:{maxPrice:{prompt:"-1"}}},{provider:{max_price:{prompt:"-1"}}}),g("max-price-malformed","reject","numeric string",{provider:{maxPrice:{prompt:"not-a-price"}}},{provider:{max_price:{prompt:"not-a-price"}}}),g("max-price-overflow","reject","numeric string",{provider:{maxPrice:{prompt:Hr}}},{provider:{max_price:{prompt:Hr}}}),g("percentile-negative","reject","non-negative",{provider:{preferredMinThroughput:{p50:-1}}},{provider:{preferred_min_throughput:{p50:-1}}}),g("valid-minimal-policy","accept",void 0,{},{}),g("timeout-one","accept",void 0,{timeoutMs:1},{timeout_ms:1}),g("timeout-max","accept",void 0,{timeoutMs:6e5},{timeout_ms:6e5}),g("max-price-zero","accept",void 0,{provider:{maxPrice:{prompt:"0"}}},{provider:{max_price:{prompt:"0"}}}),g("max-price-micro","accept",void 0,{provider:{maxPrice:{prompt:"0.000001"}}},{provider:{max_price:{prompt:"0.000001"}}}),g("max-price-positive","accept",void 0,{provider:{maxPrice:{completion:"1.5"}}},{provider:{max_price:{completion:"1.5"}}}),g("valid-complete-policy","accept",void 0,{models:["openrouter/fallback"],timeoutMs:1,httpReferer:"https://example.test",appTitle:"gateway",provider:{allowFallbacks:false,requireParameters:true,zdr:true,dataCollection:"deny",enforceDistillableText:true,only:["anthropic"],ignore:["openai"],order:["anthropic"],sort:{by:"exacto",partition:"none"},quantizations:["fp16"],maxPrice:{prompt:"0",completion:"1.5",request:"2",image:"3",audio:"4"},preferredMinThroughput:{p50:0,p75:1,p90:2,p99:3},preferredMaxLatency:1.5}},{models:["openrouter/fallback"],timeout_ms:1,http_referer:"https://example.test",x_open_router_title:"gateway",provider:{allow_fallbacks:false,require_parameters:true,zdr:true,data_collection:"deny",enforce_distillable_text:true,only:["anthropic"],ignore:["openai"],order:["anthropic"],sort:{by:"exacto",partition:"none"},quantizations:["fp16"],max_price:{prompt:"0",completion:"1.5",request:"2",image:"3",audio:"4"},preferred_min_throughput:{p50:0,p75:1,p90:2,p99:3},preferred_max_latency:1.5}})];var pe=Object.freeze({chat:true,stream:true,tools:false,agents:false,embeddings:false,images:false,audio:false,structuredOutput:false,attach:false}),lt=["init","test","build","start"];function h(e){return `${e.join(`
4729
4738
  `)}
4730
4739
  `}function ct(e){return h(["# Required. Server-side only. Never expose this value to browsers, frontend","# environment prefixes, client bundles, or committed files.","OPENROUTER_API_KEY=","","# Required. Choose a model slug at runtime. Workspai does not select a billable","# model for you. List current slugs at https://openrouter.ai/models","OPENROUTER_MODEL=","",...e.attributionComment,`${e.refererName}=`,`${e.titleName}=`,"","# Optional timeout in milliseconds. Defaults to 30000 when omitted.","OPENROUTER_TIMEOUT_MS="])}async function Qr(e){let t=e.generatedAt??new Date().toISOString(),r=h$2(),o=e.kit.runtime==="python"?"pip":"npm";await M.ensureDir(e.projectPath),await e$1(x.join(e.projectPath,".workspai","context.json"),`${JSON.stringify({engine:o,runtime:e.kit.runtime,framework:e.kit.gatewayId,kind:tt,category:Mt,kit:e.kit.id},null,2)}
4731
4740
  `),await e$1(x.join(e.projectPath,".workspai","project.json"),`${JSON.stringify({schema_version:"1.0",name:e.projectName,slug:e.projectName,kind:tt,project_type:tt,category:Mt,runtime:e.kit.runtime,framework:e.kit.gatewayId,framework_display_name:e.kit.gatewayName,kit_name:e.kit.id,kit:e.kit.id,engine:o,support_tier:"extended",module_support:false,modules:[],workspai_version:r,rapidkit_version:r,generated_by:"workspai",generated_at:t,contracts:{owns:["model-gateway"],apis:[],publishes:[],consumes:e.kit.gatewayId?[e.kit.gatewayId]:[],dependsOn:[],env:[...e.kit.requiredEnvironment]}},null,2)}
4732
4741
  `);}var eo=rt("openrouter-python");if(!eo)throw new Error("Missing OpenRouter Python gateway baseline.");var to=ce(eo,"openrouter");function Bn(e){return h(["[build-system]",'requires = ["setuptools>=68"]','build-backend = "setuptools.build_meta"',"","[project]",`name = "${e}"`,'version = "0.1.0"','description = "Server-owned OpenRouter Model Gateway generated by Workspai."','requires-python = ">=3.10"',"dependencies = [",` "openrouter==${to}",`,"]","","[project.scripts]",'start = "model_gateway.main:main"',"","[tool.setuptools.packages.find]",'where = ["src"]'])}function zn(){return `${JSON.stringify({models:[],provider:{allow_fallbacks:null,order:null,only:null,ignore:null,sort:null,quantizations:null,require_parameters:null,zdr:null,data_collection:null,enforce_distillable_text:null,max_price:null,preferred_min_throughput:null,preferred_max_latency:null},timeout_ms:3e4,http_referer:null,x_open_router_title:null},null,2)}
4733
- `}function Yn(){return ct({attributionComment:["# Optional application attribution. These map to the official Python SDK","# constructor fields http_referer and x_open_router_title, and to the SDK","# environment names OPENROUTER_HTTP_REFERER and OPENROUTER_X_OPEN_ROUTER_TITLE."],refererName:"OPENROUTER_HTTP_REFERER",titleName:"OPENROUTER_X_OPEN_ROUTER_TITLE"})}function Xn(e){return h([`# ${e}`,"","This is a server-owned OpenRouter Model Gateway generated by Workspai.","It is an AI Gateway, not an agent runtime. OpenRouter owns provider and","model routing. Workspai owns generation, lifecycle, and verification.","",`The official Client SDK is \`openrouter\` \`${to}\`.`,"A model identifier is runtime configuration, not a project kit.","","## Security","","- Keep `OPENROUTER_API_KEY` on the server. Do not put it in browser bundles or"," client-side environment prefixes.","- Diagnostics redact Authorization headers, API keys, URL secrets, and request bodies.","- Configuration is validated before any network request.","- This project does not disable TLS verification and does not enable SDK debug logging.","- Privacy and routing policy live in `gateway.policy.json` using Python SDK field names.","- When a privacy or routing field is omitted, OpenRouter upstream defaults remain in"," effect. The SDK documents `data_collection` default as `allow` when unset.","","## Configure","","1. Copy `.env.example` into your secret store or shell. Do not commit `.env`.","2. Set `OPENROUTER_API_KEY` and `OPENROUTER_MODEL`.","3. Edit `gateway.policy.json` for official Python SDK provider controls:"," fallbacks, order/allow/deny, sort (`price`/`throughput`/`latency`/`exacto`,"," optional `{ by, partition }` where `partition` is `model` or `none`),"," quantizations, require_parameters, ZDR, data_collection, enforce_distillable_text,"," max_price, and preferred throughput or latency. Unknown fields are rejected.","",'Contradictory policy fails before a request is sent. ZDR and `data_collection: "deny"`',"are never silently dropped. Application-side retries are set to strategy `none` so they","cannot duplicate billable requests; OpenRouter still applies provider fallbacks when","`allow_fallbacks` is not false. An empty successful completion returns an empty string.","`max_price` values must be numeric strings that stay finite after interpretation;","overflow-to-infinity strings are rejected. Streaming cleanup prefers the SDK iterator","`close()`/`return()` path, then `EventStream.close()`, then `cancel()`, and never","invokes two of those on the same stream.","","Timeout must be a bounded positive integer (1 to 600000 ms). `gateway.policy.json` is","resolved from the installed package location, then the project root, then the process","working directory. The `start` console script uses that same lookup, so it can run from","another directory after an editable install.","","Capabilities such as tools, JSON schema, streaming, images, audio, and embeddings","vary by model and provider. This starter only claims chat generation and streaming.","","## Commands","","```bash","python3 -m venv .venv","source .venv/bin/activate","python -m pip install -e .","python -m compileall .","python -m unittest discover -s tests","python main.py --smoke","python main.py","```","","On Windows PowerShell, activate with `.venv\\Scripts\\Activate.ps1`.","","`unittest` and `--smoke` inject a fake httpx transport. They do not call OpenRouter","and do not require a real `OPENROUTER_API_KEY`. `python main.py` and the `start`","console script are live entrypoints unless `--smoke` is passed.","","Workspace Run uses the same layout: `init` installs the editable project, `test` runs","`unittest`, `build` runs `compileall`, and `start` runs `main.py`.","","## Future composition","","An agent runtime can later consume this Model Gateway port. Do not copy these","templates into an Agent Framework kit; compose against the port instead."])}function Hn(){return h(["from __future__ import annotations","","import os","import re","from typing import Mapping","",'_SECRET = re.compile(r"(?:sk-or-[A-Za-z0-9_-]+|sk-[A-Za-z0-9_-]{8,}|Bearer\\s+[A-Za-z0-9._~+/=-]+)", re.I)',`_AUTH = re.compile(r"authorization\\s*[:=]\\s*[\\"\\']?[^\\"\\'\\s]+", re.I)`,'_URL_SECRET = re.compile(r"([?&](?:api[_-]?key|token|secret|password)=)[^&\\s]+", re.I)',"","","def redact_secret_shaped_values(value: str) -> str:",' live_key = os.environ.get("OPENROUTER_API_KEY") or ""',' output = value.replace(live_key, "[redacted]") if live_key else value',' output = _SECRET.sub("[redacted]", output)',' output = _AUTH.sub("Authorization: [redacted]", output)',' return _URL_SECRET.sub(r"\\1[redacted]", output)',"","","def redact_headers(headers: Mapping[str, str]) -> dict[str, str]:"," redacted: dict[str, str] = {}"," for key, value in headers.items():",' if key.lower() == "authorization":',' redacted[key] = "[redacted]"'," else:"," redacted[key] = redact_secret_shaped_values(str(value))"," return redacted"])}function Jn(){return h(["from __future__ import annotations","","from dataclasses import dataclass","from typing import Any, Mapping, Optional","","from .redact import redact_headers, redact_secret_shaped_values","","","class GatewayConfigurationError(Exception):"," pass","","","@dataclass","class GatewayError:"," message: str"," status_code: Optional[int] = None"," request_id: Optional[str] = None"," retry_after: Optional[str] = None"," safe_headers: Optional[dict[str, str]] = None","","","class GatewayUpstreamError(RuntimeError):"," def __init__(self, mapped: GatewayError) -> None:",' suffix = f" (status {mapped.status_code})" if mapped.status_code else ""',' request = f" request {mapped.request_id}" if mapped.request_id else ""',' super().__init__(f"{mapped.message}{suffix}{request}")'," self.status_code = mapped.status_code"," self.request_id = mapped.request_id"," self.retry_after = mapped.retry_after"," self.safe_headers = mapped.safe_headers","","","def _as_headers(value: Any) -> Any:"," if value is None:"," return None",' response = getattr(value, "response", None)',' headers = getattr(value, "headers", None)'," if headers is None and response is not None:",' headers = getattr(response, "headers", None)'," return headers","","","def _header(headers: Any, name: str) -> Optional[str]:"," if headers is None:"," return None",' getter = getattr(headers, "get", None)'," if callable(getter):"," value = getter(name) or getter(name.lower())"," return str(value) if value is not None else None"," if isinstance(headers, Mapping):"," lowered = {str(key).lower(): str(item) for key, item in headers.items()}"," return lowered.get(name.lower())"," return None","","","def _status_code(error: object) -> Optional[int]:",' for attr in ("status_code", "status"):'," value = getattr(error, attr, None)"," if isinstance(value, int):"," return value",' response = getattr(error, "response", None)',' value = getattr(response, "status_code", None) if response is not None else None'," return value if isinstance(value, int) else None","","","def map_upstream_error(error: object) -> GatewayError:"," headers = _as_headers(error)"," safe_headers = redact_headers(headers) if headers is not None else None"," return GatewayError("," message=redact_secret_shaped_values(str(error)),"," status_code=_status_code(error),",' request_id=_header(safe_headers, "x-request-id") or _header(headers, "x-request-id") or _header(headers, "x-openrouter-request-id"),',' retry_after=_header(safe_headers, "retry-after") or _header(headers, "retry-after"),'," safe_headers=safe_headers,"," )","","","def to_gateway_upstream_error(error: object) -> GatewayUpstreamError:"," if isinstance(error, GatewayUpstreamError):"," return error"," return GatewayUpstreamError(map_upstream_error(error))"])}function Zn(){return h(["from __future__ import annotations","","from typing import Iterator, Protocol","","","class ModelGateway(Protocol):"," def generate(self, messages: list[dict[str, str]]) -> str: ...",""," def stream(self, messages: list[dict[str, str]]) -> Iterator[str]: ...",""," def close(self) -> None: ..."])}function Qn(){let e=we.map(n=>`"${n}"`).join(", "),t=ke.map(n=>`"${n}"`).join(", "),r=at.map(n=>`"${n}"`).join(", "),o=Ee.map(n=>`"${n}"`).join(", "),a=nt.map(n=>`"${n}"`).join(", "),s=ot.map(n=>`"${n}"`).join(", ");return h(["from __future__ import annotations","","import json","import math","import os","import re","from dataclasses import dataclass, field","from pathlib import Path","from typing import Any","","from .errors import GatewayConfigurationError","",`POLICY_FILE_NAME = ${JSON.stringify(de)}`,`DEFAULT_TIMEOUT_MS = ${3e4}`,`MAX_TIMEOUT_MS = ${6e5}`,`SORT_VALUES = {${e}}`,`PARTITION_VALUES = {${t}}`,`DATA_COLLECTION_VALUES = {${r}}`,`QUANTIZATION_VALUES = {${o}}`,`MAX_PRICE_KEYS = {${a}}`,`PERCENTILE_KEYS = {${s}}`,'ROOT_KEYS = {"models", "provider", "timeout_ms", "http_referer", "x_open_router_title"}',"PROVIDER_KEYS = {",' "allow_fallbacks",',' "order",',' "only",',' "ignore",',' "sort",',' "quantizations",',' "require_parameters",',' "zdr",',' "data_collection",',' "enforce_distillable_text",',' "max_price",',' "preferred_min_throughput",',' "preferred_max_latency",',"}",'_PRICE = re.compile(r"^(?:0|[1-9]\\d*)(?:\\.\\d+)?$")',"","","@dataclass","class GatewayConfig:"," api_key: str"," model: str"," models: list[str] = field(default_factory=list)"," timeout_ms: int = DEFAULT_TIMEOUT_MS"," http_referer: str | None = None"," x_open_router_title: str | None = None"," provider: dict[str, Any] | None = None","","","def _reject_unknown(record: dict[str, Any], allowed: set[str], label: str) -> None:"," unknown = sorted(key for key in record if key not in allowed)"," if unknown:",' raise GatewayConfigurationError(label + " contains unknown field(s): " + ", ".join(unknown))',"","","def _optional_bool(value: object, label: str) -> bool | None:"," if value is None:"," return None"," if not isinstance(value, bool):",' raise GatewayConfigurationError(f"{label} must be a boolean.")'," return value","","","def _optional_string(value: object, label: str) -> str | None:",' if value is None or value == "":'," return None"," if not isinstance(value, str):",' raise GatewayConfigurationError(f"{label} must be a string.")'," return value.strip() or None","","","def _string_list(value: object, label: str) -> list[str]:"," if value is None:"," return []"," if not isinstance(value, list):",' raise GatewayConfigurationError(f"{label} must be an array of non-empty strings.")'," items: list[str] = []"," for index, item in enumerate(value):"," if not isinstance(item, str) or not item.strip():",' raise GatewayConfigurationError(f"{label}[{index}] must be a non-empty string.")'," items.append(item.strip())"," seen: set[str] = set()"," duplicates: list[str] = []"," for item in items:"," if item in seen:"," duplicates.append(item)"," seen.add(item)"," if duplicates:",' raise GatewayConfigurationError(label + " contains duplicates: " + ", ".join(dict.fromkeys(duplicates)))'," return items","","","def _policy_int(value: object, label: str) -> int:"," if type(value) is not int or isinstance(value, bool) or value <= 0 or value > MAX_TIMEOUT_MS:",' raise GatewayConfigurationError(f"{label} must be a positive integer between 1 and {MAX_TIMEOUT_MS}.")'," return value","","","def _env_int(value: str, label: str) -> int:"," trimmed = value.strip()",' if not re.fullmatch(r"[1-9]\\d*", trimmed):',' raise GatewayConfigurationError(f"{label} must be a positive integer between 1 and {MAX_TIMEOUT_MS}.")'," return _policy_int(int(trimmed), label)","","","def _non_negative_number(value: object, label: str) -> float:"," if isinstance(value, bool) or not isinstance(value, (int, float)):",' raise GatewayConfigurationError(f"{label} must be a finite non-negative number.")'," number = float(value)",' if number != number or number < 0 or number == float("inf"):',' raise GatewayConfigurationError(f"{label} must be a finite non-negative number.")'," return number","","","def _max_price(value: object) -> dict[str, str] | None:"," if value is None:"," return None"," if not isinstance(value, dict):",' raise GatewayConfigurationError("provider.max_price must be an object.")',' _reject_unknown(value, MAX_PRICE_KEYS, "provider.max_price")'," mapped: dict[str, str] = {}",' for key in ("prompt", "completion", "request", "image", "audio"):'," entry = value.get(key)"," if entry is None:"," continue"," if not isinstance(entry, str) or not _PRICE.fullmatch(entry):",' raise GatewayConfigurationError(f"provider.max_price.{key} must be a non-negative numeric string.")'," number = float(entry)"," if not math.isfinite(number) or number < 0:",' raise GatewayConfigurationError(f"provider.max_price.{key} must be a non-negative numeric string.")'," mapped[key] = entry"," return mapped","","","def _percentile_or_number(value: object, label: str) -> float | dict[str, float | None] | None:"," if value is None:"," return None"," if isinstance(value, (int, float)) and not isinstance(value, bool):"," return _non_negative_number(value, label)"," if not isinstance(value, dict):",' raise GatewayConfigurationError(f"{label} must be a number or percentile object.")'," _reject_unknown(value, PERCENTILE_KEYS, label)"," mapped: dict[str, float | None] = {}",' for key in ("p50", "p75", "p90", "p99"):'," if key not in value:"," continue"," entry = value[key]",' mapped[key] = None if entry is None else _non_negative_number(entry, f"{label}.{key}")'," return mapped","","","def _sort(value: object) -> object:"," if value is None:"," return None"," if isinstance(value, str):"," if value not in SORT_VALUES:",' raise GatewayConfigurationError("provider.sort must be price, throughput, latency, or exacto.")'," return value"," if not isinstance(value, dict):",' raise GatewayConfigurationError("provider.sort must be a string or { by, partition } object.")',' _reject_unknown(value, {"by", "partition"}, "provider.sort")',' by = value.get("by")',' partition = value.get("partition")'," if by is not None and (not isinstance(by, str) or by not in SORT_VALUES):",' raise GatewayConfigurationError("provider.sort.by must be price, throughput, latency, or exacto.")'," if partition is not None and (not isinstance(partition, str) or partition not in PARTITION_VALUES):",' raise GatewayConfigurationError("provider.sort.partition must be model or none.")',' return {"by": by, "partition": partition}',"","","def _quantizations(value: object) -> list[str] | None:"," if value is None:"," return None",' items = _string_list(value, "provider.quantizations")'," unknown = [item for item in items if item not in QUANTIZATION_VALUES]"," if unknown:",' raise GatewayConfigurationError("unsupported provider.quantizations value: " + ", ".join(unknown))'," return items","","","def default_policy_root() -> Path:"," here = Path(__file__).resolve()"," for candidate in here.parents[:8]:"," if (candidate / POLICY_FILE_NAME).is_file():"," return candidate"," cwd = Path.cwd()"," if (cwd / POLICY_FILE_NAME).is_file():"," return cwd"," return here.parents[2] if len(here.parents) > 2 else cwd","","","def load_policy_file(root: Path) -> object:"," path = root / POLICY_FILE_NAME"," try:",' return json.loads(path.read_text(encoding="utf-8"))'," except (OSError, json.JSONDecodeError, UnicodeError) as error:",' raise GatewayConfigurationError(f"Unable to read {POLICY_FILE_NAME}: {error}") from error',"","","def to_sdk_provider(provider: dict[str, Any] | None) -> dict[str, Any] | None:"," if not provider:"," return None"," mapped: dict[str, Any] = {}"," for key in (",' "allow_fallbacks",',' "order",',' "only",',' "ignore",',' "sort",',' "quantizations",',' "require_parameters",',' "zdr",',' "data_collection",',' "enforce_distillable_text",',' "max_price",',' "preferred_min_throughput",',' "preferred_max_latency",'," ):"," value = provider.get(key)"," if value not in (None, [], {}):"," mapped[key] = value"," return mapped or None","","","def _parse_policy(value: object) -> dict[str, Any]:"," if not isinstance(value, dict):",' raise GatewayConfigurationError("gateway.policy.json must be a JSON object.")',' _reject_unknown(value, ROOT_KEYS, "gateway.policy.json")',' models = None if "models" not in value else _string_list(value.get("models"), "models")',' raw_provider = value.get("provider") if "provider" in value else None'," provider: dict[str, Any] | None"," if raw_provider is None:"," provider = None"," else:"," if not isinstance(raw_provider, dict):",' raise GatewayConfigurationError("provider must be an object.")',' _reject_unknown(raw_provider, PROVIDER_KEYS, "provider")',' data_collection = raw_provider.get("data_collection")'," if data_collection is not None and data_collection not in DATA_COLLECTION_VALUES:",' raise GatewayConfigurationError("provider.data_collection must be allow or deny.")',' only = None if raw_provider.get("only") is None else _string_list(raw_provider.get("only"), "provider.only")',' ignore = None if raw_provider.get("ignore") is None else _string_list(raw_provider.get("ignore"), "provider.ignore")',' order = None if raw_provider.get("order") is None else _string_list(raw_provider.get("order"), "provider.order")'," provider = {",' "allow_fallbacks": _optional_bool(raw_provider.get("allow_fallbacks"), "provider.allow_fallbacks"),',' "require_parameters": _optional_bool(raw_provider.get("require_parameters"), "provider.require_parameters"),',' "zdr": _optional_bool(raw_provider.get("zdr"), "provider.zdr"),',' "enforce_distillable_text": _optional_bool(raw_provider.get("enforce_distillable_text"), "provider.enforce_distillable_text"),',' "data_collection": data_collection,',' "only": only,',' "ignore": ignore,',' "order": order,',' "sort": _sort(raw_provider.get("sort")),',' "quantizations": _quantizations(raw_provider.get("quantizations")),',' "max_price": _max_price(raw_provider.get("max_price")),',' "preferred_min_throughput": _percentile_or_number(raw_provider.get("preferred_min_throughput"), "provider.preferred_min_throughput"),',' "preferred_max_latency": _percentile_or_number(raw_provider.get("preferred_max_latency"), "provider.preferred_max_latency"),'," }",' timeout_ms = value.get("timeout_ms")'," return {",' "models": models,',' "provider": provider,',' "timeout_ms": None if timeout_ms is None else _policy_int(timeout_ms, "timeout_ms"),',' "http_referer": _optional_string(value.get("http_referer"), "http_referer"),',' "x_open_router_title": _optional_string(value.get("x_open_router_title"), "x_open_router_title"),'," }","","","def load_gateway_config(env: dict[str, str] | None = None, policy: object | None = None, root: Path | None = None) -> GatewayConfig:"," try:"," environ = env if env is not None else os.environ"," payload = _parse_policy(policy if policy is not None else load_policy_file(root or default_policy_root()))",' api_key = (environ.get("OPENROUTER_API_KEY") or "").strip()',' model = (environ.get("OPENROUTER_MODEL") or "").strip()'," if not api_key:",' raise GatewayConfigurationError("OPENROUTER_API_KEY is required before any OpenRouter request.")'," if not model:",' raise GatewayConfigurationError("OPENROUTER_MODEL is required. Choose a model slug; this project does not default one.")',' models = payload.get("models") or []'," if model in models:",' raise GatewayConfigurationError("models fallbacks must not repeat OPENROUTER_MODEL.")',' provider = payload.get("provider")',' only = (provider or {}).get("only") or []',' ignore = (provider or {}).get("ignore") or []',' order = (provider or {}).get("order") or []'," overlap = sorted(set(only) & set(ignore))"," if overlap:",' raise GatewayConfigurationError("provider.only and provider.ignore overlap: " + ", ".join(overlap))'," ordered_ignored = sorted(set(order) & set(ignore))"," if ordered_ignored:",' raise GatewayConfigurationError("provider.order includes ignored providers: " + ", ".join(ordered_ignored))',' if provider and provider.get("zdr") is True and provider.get("data_collection") == "allow":',' raise GatewayConfigurationError("provider.zdr=true cannot be combined with data_collection=allow.")',' timeout_raw = (environ.get("OPENROUTER_TIMEOUT_MS") or "").strip()',' timeout_ms = _env_int(timeout_raw, "OPENROUTER_TIMEOUT_MS") if timeout_raw else (',' payload["timeout_ms"] if payload.get("timeout_ms") is not None else DEFAULT_TIMEOUT_MS'," )",' http_referer = (environ.get("OPENROUTER_HTTP_REFERER") or "").strip() or payload.get("http_referer")',' title = (environ.get("OPENROUTER_X_OPEN_ROUTER_TITLE") or "").strip() or payload.get("x_open_router_title")'," return GatewayConfig("," api_key=api_key,"," model=model,"," models=models,"," timeout_ms=timeout_ms,"," http_referer=http_referer or None,"," x_open_router_title=title or None,"," provider=to_sdk_provider(provider),"," )"," except GatewayConfigurationError:"," raise"," except (AttributeError, TypeError, ValueError, KeyError) as error:",' raise GatewayConfigurationError(f"Invalid gateway policy: {error}") from error'])}function ea(){return h(["from __future__ import annotations","","from typing import Any, Iterator","","import httpx","from openrouter import OpenRouter","from openrouter.utils.retries import BackoffStrategy, RetryConfig","","from .config import GatewayConfig, load_gateway_config","from .errors import GatewayConfigurationError, to_gateway_upstream_error","","NO_RETRIES = RetryConfig(",' "none",'," BackoffStrategy(initial_interval=0, max_interval=0, exponent=1.0, max_elapsed_time=0),"," False,",")","","","def _text_from_content(value: object) -> str:"," if isinstance(value, str):"," return value"," if isinstance(value, list):",' return "".join(_text_from_content(item) for item in value)',' text = getattr(value, "content", None)'," if text is not None and text is not value:"," return _text_from_content(text)",' delta = getattr(value, "delta", None)'," if delta is not None:",' return _text_from_content(getattr(delta, "content", ""))'," if isinstance(value, dict):",' return _text_from_content(value.get("content") or value.get("text") or "")',' return "" if value is None else str(value)',"","","def _release_stream(stream: object) -> None:",' if stream is None or getattr(stream, "_gateway_released", False):'," return"," try:",' object.__setattr__(stream, "_gateway_released", True)'," except Exception: # noqa: BLE001 - release flag is best-effort"," pass",' generator = getattr(stream, "generator", None)',' iterator_close = getattr(generator, "close", None) if generator is not None else None'," if callable(iterator_close):"," try:"," iterator_close()"," except Exception: # noqa: BLE001 - cleanup must not hide the original error"," pass"," return",' closer = getattr(stream, "close", None)'," if callable(closer):"," try:"," closer()"," except Exception: # noqa: BLE001 - cleanup must not hide the original error"," pass"," return",' cancel = getattr(stream, "cancel", None)'," if callable(cancel):"," try:"," cancel()"," except Exception: # noqa: BLE001 - cleanup must not hide the original error"," pass","","","def _is_timeout(error: BaseException) -> bool:"," return isinstance(error, (TimeoutError, httpx.TimeoutException, httpx.ReadTimeout, httpx.ConnectTimeout))","","","class OpenRouterModelGateway:"," def __init__(self, config: GatewayConfig | None = None, client: httpx.Client | None = None) -> None:"," self._config = config or load_gateway_config()"," self._closed = False"," kwargs: dict[str, Any] = {",' "api_key": self._config.api_key,',' "http_referer": self._config.http_referer,',' "x_open_router_title": self._config.x_open_router_title,',' "timeout_ms": self._config.timeout_ms,',' "retry_config": NO_RETRIES,'," }"," if client is not None:",' kwargs["client"] = client'," self._sdk = OpenRouter(**kwargs)",""," def __enter__(self) -> OpenRouterModelGateway:"," return self",""," def __exit__(self, exc_type, exc, tb) -> None:"," self.close()",""," def close(self) -> None:"," if self._closed:"," return"," self._closed = True",' closer = getattr(self._sdk, "close", None)'," if callable(closer):"," closer()"," return",' exit_fn = getattr(self._sdk, "__exit__", None)'," if callable(exit_fn):"," exit_fn(None, None, None)",""," def _assert_open(self) -> None:"," if self._closed:",' raise GatewayConfigurationError("OpenRouterModelGateway is closed.")',""," def _normalize(self, error: BaseException) -> BaseException:"," if isinstance(error, GatewayConfigurationError) or _is_timeout(error):"," return error"," return to_gateway_upstream_error(error)",""," def generate(self, messages: list[dict[str, str]]) -> str:"," self._assert_open()"," if not messages:",' raise GatewayConfigurationError("messages must not be empty.")'," try:"," result = self._sdk.chat.send("," messages=messages,"," model=self._config.model,"," models=self._config.models or None,"," provider=self._config.provider,"," stream=False,"," timeout_ms=self._config.timeout_ms,"," retries=NO_RETRIES,"," )",' choices = getattr(result, "choices", None) or []',' message = getattr(choices[0], "message", None) if choices else None',' return _text_from_content(getattr(message, "content", ""))'," except GatewayConfigurationError:"," raise"," except Exception as error: # noqa: BLE001 - normalize upstream SDK errors"," raised = self._normalize(error)"," if raised is error:"," raise"," raise raised from error",""," def stream(self, messages: list[dict[str, str]]) -> Iterator[str]:"," self._assert_open()"," if not messages:",' raise GatewayConfigurationError("messages must not be empty.")'," opened: object | None = None"," try:"," opened = self._sdk.chat.send("," messages=messages,"," model=self._config.model,"," models=self._config.models or None,"," provider=self._config.provider,"," stream=True,"," timeout_ms=self._config.timeout_ms,"," retries=NO_RETRIES,"," )"," for event in opened:"," text = _text_from_content(event)"," if not text:",' choices = getattr(event, "choices", None) or []'," if choices:"," text = _text_from_content(choices[0])"," if text:"," yield text"," except GatewayConfigurationError:"," raise"," except Exception as error: # noqa: BLE001 - normalize open and iteration errors"," raised = self._normalize(error)"," if raised is error:"," raise"," raise raised from error"," finally:"," _release_stream(opened)"])}function ta(){return h(["from __future__ import annotations","","import json","import os","import sys","","import httpx","","from .config import load_gateway_config","from .errors import GatewayConfigurationError, map_upstream_error","from .gateway import OpenRouterModelGateway","","","def _prompt() -> str:",' args = [arg for arg in sys.argv[1:] if arg != "--smoke"]',' return " ".join(args).strip() or "Reply with a short acknowledgement."',"","",'def _chat_completion_payload(content: str, model: str = "openrouter/test-model") -> dict:'," return {",' "id": "smoke-non-stream",',' "created": 1,',' "model": model,',' "object": "chat.completion",',' "system_fingerprint": None,',' "choices": [{"finish_reason": "stop", "index": 0, "message": {"role": "assistant", "content": content}}],'," }","","","def _chat_stream_sse(parts: list[str]) -> bytes:"," events = []"," for index, content in enumerate(parts):"," payload = json.dumps("," {",' "id": "smoke-stream",',' "created": 1,',' "model": "openrouter/test-model",',' "object": "chat.completion.chunk",',' "choices": ['," {",' "delta": {"content": content},',' "finish_reason": "stop" if index == len(parts) - 1 else None,',' "index": 0,'," }"," ],"," }"," )",' events.append(f"data: {payload}\\n\\n")',' events.append("data: [DONE]\\n\\n")',' return "".join(events).encode("utf-8")',"","","def _smoke_handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream", "x-request-id": "smoke-stream"}, content=_chat_stream_sse(["smoke"]))'," parsed = json.loads(body) if body else {}"," return httpx.Response("," 200,",' headers={"content-type": "application/json", "x-request-id": "smoke-id"},',' json=_chat_completion_payload("smoke-ok", parsed.get("model") or "openrouter/test-model"),'," )","","","def run_smoke() -> None:",' os.environ.setdefault("OPENROUTER_API_KEY", "test-openrouter-key")',' os.environ.setdefault("OPENROUTER_MODEL", "openrouter/test-model")'," transport = httpx.MockTransport(_smoke_handler)"," with httpx.Client(transport=transport) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(), client=http_client) as gateway:",' generated = gateway.generate([{"role": "user", "content": "ping"}])',' streamed = "".join(gateway.stream([{"role": "user", "content": "ping"}]))',' sys.stdout.write(f"{generated}:{streamed}\\n")',"","","def main() -> None:",' if "--smoke" in sys.argv:'," run_smoke()"," return"," try:"," with OpenRouterModelGateway() as gateway:"," wrote = False",' for chunk in gateway.stream([{"role": "user", "content": _prompt()}]):'," sys.stdout.write(chunk)"," wrote = True"," if not wrote:",' sys.stdout.write(gateway.generate([{"role": "user", "content": _prompt()}]))',' sys.stdout.write("\\n")'," except GatewayConfigurationError as error:",' sys.stderr.write(str(error) + "\\n")'," raise SystemExit(1) from error"," except Exception as error: # noqa: BLE001 - CLI boundary"," mapped = map_upstream_error(error)",' sys.stderr.write(mapped.message + "\\n")'," raise SystemExit(1) from error","","",'if __name__ == "__main__":'," main()"])}function ra(){return h(["from .config import load_gateway_config, to_sdk_provider","from .errors import GatewayConfigurationError, GatewayUpstreamError, map_upstream_error","from .gateway import OpenRouterModelGateway","from .redact import redact_secret_shaped_values","","__all__ = [",' "GatewayConfigurationError",',' "GatewayUpstreamError",',' "OpenRouterModelGateway",',' "load_gateway_config",',' "map_upstream_error",',' "redact_secret_shaped_values",',' "to_sdk_provider",',"]"])}function oa(){return h(["from model_gateway.main import main","",'if __name__ == "__main__":'," main()"])}function na(){return h(["from __future__ import annotations","","import json","import os","import tempfile","import unittest","from pathlib import Path","","import httpx","","from model_gateway.config import load_gateway_config, load_policy_file, to_sdk_provider","from model_gateway.errors import GatewayConfigurationError, GatewayUpstreamError, map_upstream_error","from model_gateway.gateway import OpenRouterModelGateway","from model_gateway.redact import redact_secret_shaped_values","","ROOT = Path(__file__).resolve().parents[1]",`PARITY_CASES = json.loads(${JSON.stringify(JSON.stringify(it))})`,"","",'def chat_completion_payload(content: str, model: str = "openrouter/test-model") -> dict:'," return {",' "id": "gen-1",',' "created": 1,',' "model": model,',' "object": "chat.completion",',' "system_fingerprint": None,',' "choices": [{"finish_reason": "stop", "index": 0, "message": {"role": "assistant", "content": content}}],'," }","","","def chat_stream_sse(parts: list[str]) -> bytes:"," events = []"," for index, content in enumerate(parts):"," payload = json.dumps("," {",' "id": "stream-1",',' "created": 1,',' "model": "openrouter/test-model",',' "object": "chat.completion.chunk",',' "choices": ['," {",' "delta": {"content": content},',' "finish_reason": "stop" if index == len(parts) - 1 else None,',' "index": 0,'," }"," ],"," }"," )",' events.append(f"data: {payload}\\n\\n")',' events.append("data: [DONE]\\n\\n")',' return "".join(events).encode("utf-8")',"","","class GatewayTests(unittest.TestCase):"," def setUp(self) -> None:",' os.environ["OPENROUTER_API_KEY"] = "sk-or-test"',' os.environ["OPENROUTER_MODEL"] = "openrouter/test-model"',""," def tearDown(self) -> None:",' os.environ.pop("OPENROUTER_TIMEOUT_MS", None)',""," def test_redacts_secrets(self) -> None:",' os.environ["OPENROUTER_API_KEY"] = "sk-or-live-secret"',' raw = "Authorization: Bearer sk-or-live-secret https://example.test/v1?api_key=sk-or-live-secret"'," redacted = redact_secret_shaped_values(raw)",' self.assertNotIn("sk-or-live-secret", redacted)',' self.assertIn("[redacted]", redacted)',""," def test_config_fails_before_network(self) -> None:",' os.environ.pop("OPENROUTER_API_KEY", None)'," with self.assertRaises(GatewayConfigurationError):"," load_gateway_config(root=ROOT)",""," def test_overlapping_provider_lists_fail(self) -> None:",' with self.assertRaisesRegex(GatewayConfigurationError, "overlap"):',' load_gateway_config(policy={"provider": {"only": ["openai"], "ignore": ["openai"]}})',""," def test_python_provider_field_names(self) -> None:"," mapped = to_sdk_provider("," {",' "allow_fallbacks": False,',' "zdr": True,',' "data_collection": "deny",',' "sort": "latency",',' "order": ["anthropic"],'," }"," )"," self.assertEqual("," mapped,"," {",' "allow_fallbacks": False,',' "zdr": True,',' "data_collection": "deny",',' "sort": "latency",',' "order": ["anthropic"],'," },"," )",""," def test_generate_uses_injected_transport(self) -> None:"," recorded: list[str] = []",""," def handler(request: httpx.Request) -> httpx.Response:",' recorded.append(request.content.decode("utf-8"))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," config = load_gateway_config(",' policy={"provider": {"zdr": True, "data_collection": "deny"}, "models": ["openrouter/fallback"]}'," )"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=config, client=http_client) as gateway:",' text = gateway.generate([{"role": "user", "content": "hi"}])',' self.assertEqual(text, "ok")'," payload = json.loads(recorded[0])",' self.assertEqual(payload["model"], "openrouter/test-model")',' self.assertEqual(payload.get("models"), ["openrouter/fallback"])',' self.assertTrue(payload["provider"]["zdr"])',' self.assertEqual(payload["provider"]["data_collection"], "deny")',""," def test_stream_closes_event_stream(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel", "lo"]))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' text = "".join(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertTrue("hel" in text or "hello" in text)',""," def test_maps_status_and_request_id(self) -> None:"," response = httpx.Response("," 429,",' headers={"authorization": "Bearer sk-or-secret", "x-request-id": "req-123", "retry-after": "2"},',' text="Authorization: Bearer sk-or-secret",'," )",""," class FakeError(Exception):"," def __init__(self) -> None:",' super().__init__("Authorization: Bearer sk-or-secret failed")'," self.status_code = 429"," self.headers = response.headers",""," mapped = map_upstream_error(FakeError())"," self.assertEqual(mapped.status_code, 429)",' self.assertEqual(mapped.request_id, "req-123")',' self.assertEqual(mapped.retry_after, "2")',' self.assertNotIn("sk-or-secret", mapped.message)',' self.assertEqual(mapped.safe_headers.get("authorization") or mapped.safe_headers.get("Authorization"), "[redacted]")',""," def test_timeout_from_env(self) -> None:",' os.environ["OPENROUTER_TIMEOUT_MS"] = "1500"'," config = load_gateway_config(policy={})"," self.assertEqual(config.timeout_ms, 1500)",""," def test_policy_file_has_no_secrets(self) -> None:",' policy = (ROOT / "gateway.policy.json").read_text(encoding="utf-8")',' self.assertNotIn("sk-", policy)',' self.assertNotIn("OPENROUTER_API_KEY", policy)',""," def test_rejects_invalid_policy_categories(self) -> None:"," cases = [",' ([], "JSON object"),',' ({"widget": True}, "unknown field"),',' ({"models": "openai/gpt"}, "models must be an array"),',' ({"models": [" "]}, "non-empty string"),',' ({"models": ["a", "a"]}, "duplicates"),',' ({"models": ["openrouter/test-model"]}, "repeat OPENROUTER_MODEL"),',' ({"provider": "openai"}, "provider must be an object"),',' ({"provider": {"fallbacks": True}}, "unknown field"),',' ({"provider": {"allow_fallbacks": "yes"}}, "boolean"),',' ({"provider": {"require_parameters": 1}}, "boolean"),',' ({"provider": {"zdr": "true"}}, "boolean"),',' ({"provider": {"only": ["openai"], "ignore": ["openai"]}}, "overlap"),',' ({"provider": {"order": ["openai"], "ignore": ["openai"]}}, "ignored providers"),',' ({"provider": {"data_collection": "never"}}, "allow or deny"),',' ({"provider": {"sort": "cheap"}}, "sort"),',' ({"provider": {"sort": {"by": "price", "partition": "all"}}}, "partition"),',' ({"provider": {"quantizations": ["int5"]}}, "quantizations"),',' ({"provider": {"max_price": {"prompt": 1}}}, "numeric string"),',' ({"provider": {"max_price": {"tokens": "1"}}}, "unknown field"),',' ({"provider": {"max_price": {"prompt": "-1"}}}, "numeric string"),',' ({"provider": {"max_price": {"prompt": "not-a-price"}}}, "numeric string"),',' ({"provider": {"max_price": {"prompt": "9" * 400}}}, "numeric string"),',' ({"provider": {"preferred_max_latency": {"p95": 1}}}, "unknown field"),',' ({"provider": {"preferred_min_throughput": {"p50": -1}}}, "non-negative"),',' ({"timeout_ms": 30.5}, "positive integer"),',' ({"timeout_ms": 0}, "positive integer"),',' ({"timeout_ms": 600001}, "positive integer"),',' ({"timeout_ms": "30000"}, "positive integer"),',' ({"http_referer": 1}, "string"),',' ({"provider": {"zdr": True, "data_collection": "allow"}}, "zdr"),'," ]"," for policy, needle in cases:"," with self.subTest(needle=needle, policy=policy):"," with self.assertRaises(GatewayConfigurationError) as raised:"," load_gateway_config(policy=policy)"," self.assertIn(needle, str(raised.exception))"," self.assertNotIsInstance(raised.exception, (AttributeError, TypeError, ValueError))",""," def test_accepts_official_provider_policy_boundaries(self) -> None:"," config = load_gateway_config("," policy={",' "models": ["openrouter/fallback"],',' "timeout_ms": 1,',' "http_referer": "https://example.test",',' "x_open_router_title": "gateway",',' "provider": {',' "allow_fallbacks": False,',' "require_parameters": True,',' "zdr": True,',' "data_collection": "deny",',' "enforce_distillable_text": True,',' "only": ["anthropic"],',' "ignore": ["openai"],',' "order": ["anthropic"],',' "sort": {"by": "exacto", "partition": "none"},',' "quantizations": ["fp16"],',' "max_price": {"prompt": "0", "completion": "1.5", "request": "2", "image": "3", "audio": "4"},',' "preferred_min_throughput": {"p50": 0, "p75": 1, "p90": 2, "p99": 3},',' "preferred_max_latency": 1.5,'," },"," }"," )"," self.assertEqual(config.timeout_ms, 1)"," mapped = to_sdk_provider(config.provider)",' self.assertEqual(mapped["enforce_distillable_text"], True)',' self.assertEqual(mapped["sort"], {"by": "exacto", "partition": "none"})',' self.assertEqual(mapped["max_price"]["prompt"], "0")',' upper = load_gateway_config(policy={"timeout_ms": 600000})'," self.assertEqual(upper.timeout_ms, 600000)",' tiny = load_gateway_config(policy={"provider": {"max_price": {"prompt": "0.000001"}}})',' self.assertEqual(to_sdk_provider(tiny.provider)["max_price"]["prompt"], "0.000001")',""," def test_policy_parity_corpus_executes_generated_validator(self) -> None:"," for case in PARITY_CASES:",' with self.subTest(id=case["id"]):',' if case["decision"] == "reject":'," with self.assertRaises(GatewayConfigurationError) as raised:",' load_gateway_config(policy=case["python"])',' needle = case.get("needle")'," if needle:"," self.assertIn(needle, str(raised.exception))"," self.assertNotIsInstance(raised.exception, (AttributeError, TypeError, ValueError))"," else:",' config = load_gateway_config(policy=case["python"])'," mapped = to_sdk_provider(config.provider)",' if case["id"] == "max-price-zero":',' self.assertEqual(mapped["max_price"]["prompt"], "0")',' if case["id"] == "max-price-micro":',' self.assertEqual(mapped["max_price"]["prompt"], "0.000001")',' if case["id"] == "max-price-positive":',' self.assertEqual(mapped["max_price"]["completion"], "1.5")',""," def test_finite_max_price_strings_are_preserved_or_rejected(self) -> None:"," accepted = load_gateway_config(",' policy={"provider": {"max_price": {"prompt": "0", "completion": "0.000001", "request": "12.5"}}}'," )",' mapped = to_sdk_provider(accepted.provider)["max_price"]',' self.assertEqual(mapped["prompt"], "0")',' self.assertEqual(mapped["completion"], "0.000001")',' self.assertEqual(mapped["request"], "12.5")',' for value in ("-1", "not-a-price", "9" * 400, "1e6", "+1", "inf", "nan"):'," with self.subTest(value=value):"," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"max_price": {"prompt": value}}})'," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"max_price": {"tokens": "1"}}})',""," def test_rejects_non_finite_numeric_policy_values(self) -> None:"," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"preferred_max_latency": float("inf")}})'," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"preferred_min_throughput": float("nan")}})'," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"preferred_max_latency": {"p50": float("-inf")}}})',""," def test_missing_policy_file_is_configuration_error(self) -> None:"," with tempfile.TemporaryDirectory() as directory:"," with self.assertRaises(GatewayConfigurationError) as raised:"," load_policy_file(Path(directory))",' self.assertIn("Unable to read", str(raised.exception))',""," def test_malformed_policy_json_is_configuration_error(self) -> None:"," with tempfile.TemporaryDirectory() as directory:"," root = Path(directory)",' (root / "gateway.policy.json").write_text("{", encoding="utf-8")'," with self.assertRaises(GatewayConfigurationError):"," load_policy_file(root)",""," def test_empty_messages_fail_before_transport(self) -> None:",' called = {"value": False}',""," def handler(request: httpx.Request) -> httpx.Response:",' called["value"] = True',' return httpx.Response(200, json=chat_completion_payload("nope"))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises(GatewayConfigurationError):"," gateway.generate([])"," with self.assertRaises(GatewayConfigurationError):"," next(gateway.stream([]))",' self.assertFalse(called["value"])',""," def test_empty_successful_completion_returns_empty_string(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, json=chat_completion_payload(""))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "")',""," def test_close_is_idempotent(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, json=chat_completion_payload("ok"))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," gateway = OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client)"," gateway.close()"," gateway.close()"," with self.assertRaises(GatewayConfigurationError):",' gateway.generate([{"role": "user", "content": "hi"}])',""," def test_stream_open_failure_is_redacted_upstream_error(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:"," return httpx.Response("," 401,",' headers={"authorization": "Bearer sk-or-secret", "x-request-id": "req-open", "retry-after": "7", "content-type": "application/json"},',' json={"error": {"message": "Authorization: Bearer sk-or-secret"}},'," )",""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises(GatewayUpstreamError) as raised:",' next(gateway.stream([{"role": "user", "content": "hi"}]))'," self.assertEqual(raised.exception.status_code, 401)",' self.assertEqual(raised.exception.request_id, "req-open")',' self.assertEqual(raised.exception.retry_after, "7")',' self.assertNotIn("sk-or-secret", str(raised.exception))',""," def test_mid_stream_failure_is_normalized_and_cleaned_up(self) -> None:",' closed = {"value": False}',""," def body():"," try:",' yield chat_stream_sse(["hel"]).replace(b"data: [DONE]\\n\\n", b"")',' raise RuntimeError("Authorization: Bearer sk-or-secret mid-stream")'," finally:",' closed["value"] = True',""," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=body())',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises((GatewayUpstreamError, RuntimeError)) as raised:",' list(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertNotIn("sk-or-secret", str(raised.exception))',' self.assertTrue(closed["value"])',""," def test_consumer_early_termination_closes_upstream_stream(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel"]))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' iterator = gateway.stream([{"role": "user", "content": "hi"}])'," chunk = next(iterator)",' self.assertIn("hel", chunk)'," iterator.close()",' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")',""," def _track_event_stream_cleanup(self):"," from openrouter.utils.eventstreaming import EventStream","",' counts = {"return": 0, "close": 0}'," original_init = EventStream.__init__"," original_close = EventStream.close",""," def tracked_init(self, *args, **kwargs):"," original_init(self, *args, **kwargs)"," inner = self.generator",""," class Probe:"," def __iter__(self_inner):"," return self_inner",""," def __next__(self_inner):"," return next(inner)",""," def close(self_inner):",' counts["return"] += 1'," return inner.close()",""," self.generator = Probe()",""," def tracked_close(self):",' counts["close"] += 1'," return original_close(self)",""," EventStream.__init__ = tracked_init"," EventStream.close = tracked_close"," return EventStream, original_init, original_close, counts",""," def test_normal_stream_completion_releases_iterator_once(self) -> None:"," EventStream, original_init, original_close, counts = self._track_event_stream_cleanup()",""," def handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel"]))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," try:"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' self.assertIn("hel", "".join(gateway.stream([{"role": "user", "content": "hi"}])))',' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")'," finally:"," EventStream.__init__ = original_init"," EventStream.close = original_close",' self.assertEqual(counts["return"], 1)',' self.assertEqual(counts["close"], 0)',""," def test_early_generator_close_releases_iterator_once(self) -> None:"," EventStream, original_init, original_close, counts = self._track_event_stream_cleanup()",""," def handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel", "lo"]))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," try:"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' iterator = gateway.stream([{"role": "user", "content": "hi"}])',' self.assertIn("hel", next(iterator))'," iterator.close()"," iterator.close()",' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")'," finally:"," EventStream.__init__ = original_init"," EventStream.close = original_close",' self.assertEqual(counts["return"], 1)',' self.assertEqual(counts["close"], 0)',""," def test_mid_stream_error_releases_iterator_once(self) -> None:"," EventStream, original_init, original_close, counts = self._track_event_stream_cleanup()",""," def body():",' yield chat_stream_sse(["hel"]).replace(b"data: [DONE]\\n\\n", b"")',' raise RuntimeError("Authorization: Bearer sk-or-secret mid-stream")',""," def handler(request: httpx.Request) -> httpx.Response:",' body_text = request.content.decode("utf-8")',` if '"stream": true' in body_text or '"stream":true' in body_text:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=body())',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," try:"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises((GatewayUpstreamError, RuntimeError)) as raised:",' list(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertNotIn("sk-or-secret", str(raised.exception))',' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")'," finally:"," EventStream.__init__ = original_init"," EventStream.close = original_close",' self.assertEqual(counts["return"], 1)',' self.assertEqual(counts["close"], 0)',""," def test_cleanup_failure_does_not_mask_upstream_or_success(self) -> None:"," from openrouter.utils.eventstreaming import EventStream",""," original_init = EventStream.__init__",""," def tracked_init(self, *args, **kwargs):"," original_init(self, *args, **kwargs)"," inner = self.generator",""," class Probe:"," def __iter__(self_inner):"," return self_inner",""," def __next__(self_inner):"," return next(inner)",""," def close(self_inner):"," inner.close()",' raise RuntimeError("cleanup-failed")',""," self.generator = Probe()",""," EventStream.__init__ = tracked_init"," try:"," def success_handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel"]))',""," def failing_body():",' yield chat_stream_sse(["hel"]).replace(b"data: [DONE]\\n\\n", b"")',' raise RuntimeError("Authorization: Bearer sk-or-secret mid-stream")',""," def fail_handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=failing_body())',""," with httpx.Client(transport=httpx.MockTransport(success_handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' self.assertIn("hel", "".join(gateway.stream([{"role": "user", "content": "hi"}])))'," with httpx.Client(transport=httpx.MockTransport(fail_handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises((GatewayUpstreamError, RuntimeError)) as raised:",' list(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertNotIn("sk-or-secret", str(raised.exception))',' self.assertNotIn("cleanup-failed", str(raised.exception))'," finally:"," EventStream.__init__ = original_init",""," def test_timeout_errors_remain_recognizable(self) -> None:"," class TimeoutTransport(httpx.BaseTransport):"," def handle_request(self, request: httpx.Request) -> httpx.Response:",' raise httpx.TimeoutException("Request timed out")',""," with httpx.Client(transport=TimeoutTransport()) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises(httpx.TimeoutException):",' gateway.generate([{"role": "user", "content": "hi"}])'," with self.assertRaises(httpx.TimeoutException):",' next(gateway.stream([{"role": "user", "content": "hi"}]))',""," def test_policy_root_does_not_require_process_cwd(self) -> None:"," from model_gateway.config import default_policy_root",""," resolved = default_policy_root()",' self.assertTrue((resolved / "gateway.policy.json").is_file())',"","",'if __name__ == "__main__":'," unittest.main()"])}async function aa(e){let t=Yr(e.projectName);await e$1(x.join(e.projectPath,"pyproject.toml"),Bn(t)),await e$1(x.join(e.projectPath,de),zn()),await e$1(x.join(e.projectPath,".env.example"),Yn()),await e$1(x.join(e.projectPath,".gitignore"),st),await e$1(x.join(e.projectPath,"README.md"),Xn(e.projectName)),await e$1(x.join(e.projectPath,"main.py"),oa()),await e$1(x.join(e.projectPath,"src","model_gateway","__init__.py"),ra()),await e$1(x.join(e.projectPath,"src","model_gateway","redact.py"),Hn()),await e$1(x.join(e.projectPath,"src","model_gateway","errors.py"),Jn()),await e$1(x.join(e.projectPath,"src","model_gateway","port.py"),Zn()),await e$1(x.join(e.projectPath,"src","model_gateway","config.py"),Qn()),await e$1(x.join(e.projectPath,"src","model_gateway","gateway.py"),ea()),await e$1(x.join(e.projectPath,"src","model_gateway","main.py"),ta()),await e$1(x.join(e.projectPath,"tests","test_gateway.py"),na());}var ro={id:"openrouter-python",gatewayId:"openrouter",gatewayName:"OpenRouter",kitId:"gateway.openrouter.python",aliases:["gateway.openrouter.python","openrouter.python","gateway.openrouter.py","openrouter-python"],label:"OpenRouter \xB7 Python",runtime:"python",requiredEnvironment:["OPENROUTER_API_KEY","OPENROUTER_MODEL"],versionBaselineId:"openrouter-python",capabilities:pe,lifecycle:{stages:lt},attach:"unsupported",generate:aa};var dt=rt("openrouter-typescript");if(!dt)throw new Error("Missing OpenRouter TypeScript gateway baseline.");var oo=ce(dt,"@openrouter/sdk"),sa=ce(dt,"typescript"),ia=ce(dt,"@types/node");function la(e){return `${JSON.stringify({name:e,version:"0.1.0",private:true,type:"module",engines:{node:">=20"},scripts:{build:"tsc --pretty false",typecheck:"tsc --noEmit --pretty false",test:"tsc --pretty false && node --test dist/tests/gateway.test.js",start:"tsc --pretty false && node dist/src/main.js",smoke:"tsc --pretty false && node dist/src/main.js --smoke"},dependencies:{"@openrouter/sdk":oo},devDependencies:{"@types/node":ia,typescript:sa}},null,2)}
4742
+ `}function Yn(){return ct({attributionComment:["# Optional application attribution. These map to the official Python SDK","# constructor fields http_referer and x_open_router_title, and to the SDK","# environment names OPENROUTER_HTTP_REFERER and OPENROUTER_X_OPEN_ROUTER_TITLE."],refererName:"OPENROUTER_HTTP_REFERER",titleName:"OPENROUTER_X_OPEN_ROUTER_TITLE"})}function Xn(e){return h([`# ${e}`,"","This is a server-owned OpenRouter Model Gateway generated by Workspai.","It is an AI Gateway, not an agent runtime. OpenRouter owns provider and","model routing. Workspai owns generation, lifecycle, and verification.","",`The official Client SDK is \`openrouter\` \`${to}\`.`,"A model identifier is runtime configuration, not a project kit.","","## Security","","- Keep `OPENROUTER_API_KEY` on the server. Do not put it in browser bundles or"," client-side environment prefixes.","- Diagnostics redact Authorization headers, API keys, URL secrets, and request bodies.","- Configuration is validated before any network request.","- This project does not disable TLS verification and does not enable SDK debug logging.","- Privacy and routing policy live in `gateway.policy.json` using Python SDK field names.","- When a privacy or routing field is omitted, OpenRouter upstream defaults remain in"," effect. The SDK documents `data_collection` default as `allow` when unset.","","## Configure","","1. Copy `.env.example` into your secret store or shell. Do not commit `.env`.","2. Set `OPENROUTER_API_KEY` and `OPENROUTER_MODEL`.","3. Edit `gateway.policy.json` for official Python SDK provider controls:"," fallbacks, order/allow/deny, sort (`price`/`throughput`/`latency`/`exacto`,"," optional `{ by, partition }` where `partition` is `model` or `none`),"," quantizations, require_parameters, ZDR, data_collection, enforce_distillable_text,"," max_price, and preferred throughput or latency. Unknown fields are rejected.","",'Contradictory policy fails before a request is sent. ZDR and `data_collection: "deny"`',"are never silently dropped. Application-side retries are set to strategy `none` so they","cannot duplicate billable requests; OpenRouter still applies provider fallbacks when","`allow_fallbacks` is not false. An empty successful completion returns an empty string.","`max_price` values must be numeric strings that stay finite after interpretation;","overflow-to-infinity strings are rejected. Streaming cleanup prefers the SDK iterator","`close()`/`return()` path, then `EventStream.close()`, then `cancel()`, and never","invokes two of those on the same stream.","","Timeout must be a bounded positive integer (1 to 600000 ms). `gateway.policy.json` is","resolved from the installed package location, then the project root, then the process","working directory. The `start` console script uses that same lookup, so it can run from","another directory after an editable install.","","Capabilities such as tools, JSON schema, streaming, images, audio, and embeddings","vary by model and provider. This starter only claims chat generation and streaming.","","## Commands","","```bash","python3 -m venv .venv","source .venv/bin/activate","python -m pip install -e .","python -m compileall .","python -m unittest discover -s tests","python main.py --smoke","python main.py","```","","On Windows PowerShell, activate with `.venv\\Scripts\\Activate.ps1`.","","`unittest` and `--smoke` inject a fake httpx transport. They do not call OpenRouter","and do not require a real `OPENROUTER_API_KEY`. `python main.py` and the `start`","console script are live entrypoints unless `--smoke` is passed.","","Workspace Run uses the same layout: `init` installs the editable project, `test` runs","`unittest`, `build` runs `compileall`, and `start` runs `main.py`.","","## Future composition","","An agent runtime can later consume this Model Gateway port. Do not copy these","templates into an Agent Framework kit; compose against the port instead."])}function Jn(){return h(["from __future__ import annotations","","import os","import re","from typing import Mapping","",'_SECRET = re.compile(r"(?:sk-or-[A-Za-z0-9_-]+|sk-[A-Za-z0-9_-]{8,}|Bearer\\s+[A-Za-z0-9._~+/=-]+)", re.I)',`_AUTH = re.compile(r"authorization\\s*[:=]\\s*[\\"\\']?[^\\"\\'\\s]+", re.I)`,'_URL_SECRET = re.compile(r"([?&](?:api[_-]?key|token|secret|password)=)[^&\\s]+", re.I)',"","","def redact_secret_shaped_values(value: str) -> str:",' live_key = os.environ.get("OPENROUTER_API_KEY") or ""',' output = value.replace(live_key, "[redacted]") if live_key else value',' output = _SECRET.sub("[redacted]", output)',' output = _AUTH.sub("Authorization: [redacted]", output)',' return _URL_SECRET.sub(r"\\1[redacted]", output)',"","","def redact_headers(headers: Mapping[str, str]) -> dict[str, str]:"," redacted: dict[str, str] = {}"," for key, value in headers.items():",' if key.lower() == "authorization":',' redacted[key] = "[redacted]"'," else:"," redacted[key] = redact_secret_shaped_values(str(value))"," return redacted"])}function Hn(){return h(["from __future__ import annotations","","from dataclasses import dataclass","from typing import Any, Mapping, Optional","","from .redact import redact_headers, redact_secret_shaped_values","","","class GatewayConfigurationError(Exception):"," pass","","","@dataclass","class GatewayError:"," message: str"," status_code: Optional[int] = None"," request_id: Optional[str] = None"," retry_after: Optional[str] = None"," safe_headers: Optional[dict[str, str]] = None","","","class GatewayUpstreamError(RuntimeError):"," def __init__(self, mapped: GatewayError) -> None:",' suffix = f" (status {mapped.status_code})" if mapped.status_code else ""',' request = f" request {mapped.request_id}" if mapped.request_id else ""',' super().__init__(f"{mapped.message}{suffix}{request}")'," self.status_code = mapped.status_code"," self.request_id = mapped.request_id"," self.retry_after = mapped.retry_after"," self.safe_headers = mapped.safe_headers","","","def _as_headers(value: Any) -> Any:"," if value is None:"," return None",' response = getattr(value, "response", None)',' headers = getattr(value, "headers", None)'," if headers is None and response is not None:",' headers = getattr(response, "headers", None)'," return headers","","","def _header(headers: Any, name: str) -> Optional[str]:"," if headers is None:"," return None",' getter = getattr(headers, "get", None)'," if callable(getter):"," value = getter(name) or getter(name.lower())"," return str(value) if value is not None else None"," if isinstance(headers, Mapping):"," lowered = {str(key).lower(): str(item) for key, item in headers.items()}"," return lowered.get(name.lower())"," return None","","","def _status_code(error: object) -> Optional[int]:",' for attr in ("status_code", "status"):'," value = getattr(error, attr, None)"," if isinstance(value, int):"," return value",' response = getattr(error, "response", None)',' value = getattr(response, "status_code", None) if response is not None else None'," return value if isinstance(value, int) else None","","","def map_upstream_error(error: object) -> GatewayError:"," headers = _as_headers(error)"," safe_headers = redact_headers(headers) if headers is not None else None"," return GatewayError("," message=redact_secret_shaped_values(str(error)),"," status_code=_status_code(error),",' request_id=_header(safe_headers, "x-request-id") or _header(headers, "x-request-id") or _header(headers, "x-openrouter-request-id"),',' retry_after=_header(safe_headers, "retry-after") or _header(headers, "retry-after"),'," safe_headers=safe_headers,"," )","","","def to_gateway_upstream_error(error: object) -> GatewayUpstreamError:"," if isinstance(error, GatewayUpstreamError):"," return error"," return GatewayUpstreamError(map_upstream_error(error))"])}function Zn(){return h(["from __future__ import annotations","","from typing import Iterator, Protocol","","","class ModelGateway(Protocol):"," def generate(self, messages: list[dict[str, str]]) -> str: ...",""," def stream(self, messages: list[dict[str, str]]) -> Iterator[str]: ...",""," def close(self) -> None: ..."])}function Qn(){let e=we.map(n=>`"${n}"`).join(", "),t=ke.map(n=>`"${n}"`).join(", "),r=at.map(n=>`"${n}"`).join(", "),o=Ee.map(n=>`"${n}"`).join(", "),a=nt.map(n=>`"${n}"`).join(", "),s=ot.map(n=>`"${n}"`).join(", ");return h(["from __future__ import annotations","","import json","import math","import os","import re","from dataclasses import dataclass, field","from pathlib import Path","from typing import Any","","from .errors import GatewayConfigurationError","",`POLICY_FILE_NAME = ${JSON.stringify(de)}`,`DEFAULT_TIMEOUT_MS = ${3e4}`,`MAX_TIMEOUT_MS = ${6e5}`,`SORT_VALUES = {${e}}`,`PARTITION_VALUES = {${t}}`,`DATA_COLLECTION_VALUES = {${r}}`,`QUANTIZATION_VALUES = {${o}}`,`MAX_PRICE_KEYS = {${a}}`,`PERCENTILE_KEYS = {${s}}`,'ROOT_KEYS = {"models", "provider", "timeout_ms", "http_referer", "x_open_router_title"}',"PROVIDER_KEYS = {",' "allow_fallbacks",',' "order",',' "only",',' "ignore",',' "sort",',' "quantizations",',' "require_parameters",',' "zdr",',' "data_collection",',' "enforce_distillable_text",',' "max_price",',' "preferred_min_throughput",',' "preferred_max_latency",',"}",'_PRICE = re.compile(r"^(?:0|[1-9]\\d*)(?:\\.\\d+)?$")',"","","@dataclass","class GatewayConfig:"," api_key: str"," model: str"," models: list[str] = field(default_factory=list)"," timeout_ms: int = DEFAULT_TIMEOUT_MS"," http_referer: str | None = None"," x_open_router_title: str | None = None"," provider: dict[str, Any] | None = None","","","def _reject_unknown(record: dict[str, Any], allowed: set[str], label: str) -> None:"," unknown = sorted(key for key in record if key not in allowed)"," if unknown:",' raise GatewayConfigurationError(label + " contains unknown field(s): " + ", ".join(unknown))',"","","def _optional_bool(value: object, label: str) -> bool | None:"," if value is None:"," return None"," if not isinstance(value, bool):",' raise GatewayConfigurationError(f"{label} must be a boolean.")'," return value","","","def _optional_string(value: object, label: str) -> str | None:",' if value is None or value == "":'," return None"," if not isinstance(value, str):",' raise GatewayConfigurationError(f"{label} must be a string.")'," return value.strip() or None","","","def _string_list(value: object, label: str) -> list[str]:"," if value is None:"," return []"," if not isinstance(value, list):",' raise GatewayConfigurationError(f"{label} must be an array of non-empty strings.")'," items: list[str] = []"," for index, item in enumerate(value):"," if not isinstance(item, str) or not item.strip():",' raise GatewayConfigurationError(f"{label}[{index}] must be a non-empty string.")'," items.append(item.strip())"," seen: set[str] = set()"," duplicates: list[str] = []"," for item in items:"," if item in seen:"," duplicates.append(item)"," seen.add(item)"," if duplicates:",' raise GatewayConfigurationError(label + " contains duplicates: " + ", ".join(dict.fromkeys(duplicates)))'," return items","","","def _policy_int(value: object, label: str) -> int:"," if type(value) is not int or isinstance(value, bool) or value <= 0 or value > MAX_TIMEOUT_MS:",' raise GatewayConfigurationError(f"{label} must be a positive integer between 1 and {MAX_TIMEOUT_MS}.")'," return value","","","def _env_int(value: str, label: str) -> int:"," trimmed = value.strip()",' if not re.fullmatch(r"[1-9]\\d*", trimmed):',' raise GatewayConfigurationError(f"{label} must be a positive integer between 1 and {MAX_TIMEOUT_MS}.")'," return _policy_int(int(trimmed), label)","","","def _non_negative_number(value: object, label: str) -> float:"," if isinstance(value, bool) or not isinstance(value, (int, float)):",' raise GatewayConfigurationError(f"{label} must be a finite non-negative number.")'," number = float(value)",' if number != number or number < 0 or number == float("inf"):',' raise GatewayConfigurationError(f"{label} must be a finite non-negative number.")'," return number","","","def _max_price(value: object) -> dict[str, str] | None:"," if value is None:"," return None"," if not isinstance(value, dict):",' raise GatewayConfigurationError("provider.max_price must be an object.")',' _reject_unknown(value, MAX_PRICE_KEYS, "provider.max_price")'," mapped: dict[str, str] = {}",' for key in ("prompt", "completion", "request", "image", "audio"):'," entry = value.get(key)"," if entry is None:"," continue"," if not isinstance(entry, str) or not _PRICE.fullmatch(entry):",' raise GatewayConfigurationError(f"provider.max_price.{key} must be a non-negative numeric string.")'," number = float(entry)"," if not math.isfinite(number) or number < 0:",' raise GatewayConfigurationError(f"provider.max_price.{key} must be a non-negative numeric string.")'," mapped[key] = entry"," return mapped","","","def _percentile_or_number(value: object, label: str) -> float | dict[str, float | None] | None:"," if value is None:"," return None"," if isinstance(value, (int, float)) and not isinstance(value, bool):"," return _non_negative_number(value, label)"," if not isinstance(value, dict):",' raise GatewayConfigurationError(f"{label} must be a number or percentile object.")'," _reject_unknown(value, PERCENTILE_KEYS, label)"," mapped: dict[str, float | None] = {}",' for key in ("p50", "p75", "p90", "p99"):'," if key not in value:"," continue"," entry = value[key]",' mapped[key] = None if entry is None else _non_negative_number(entry, f"{label}.{key}")'," return mapped","","","def _sort(value: object) -> object:"," if value is None:"," return None"," if isinstance(value, str):"," if value not in SORT_VALUES:",' raise GatewayConfigurationError("provider.sort must be price, throughput, latency, or exacto.")'," return value"," if not isinstance(value, dict):",' raise GatewayConfigurationError("provider.sort must be a string or { by, partition } object.")',' _reject_unknown(value, {"by", "partition"}, "provider.sort")',' by = value.get("by")',' partition = value.get("partition")'," if by is not None and (not isinstance(by, str) or by not in SORT_VALUES):",' raise GatewayConfigurationError("provider.sort.by must be price, throughput, latency, or exacto.")'," if partition is not None and (not isinstance(partition, str) or partition not in PARTITION_VALUES):",' raise GatewayConfigurationError("provider.sort.partition must be model or none.")',' return {"by": by, "partition": partition}',"","","def _quantizations(value: object) -> list[str] | None:"," if value is None:"," return None",' items = _string_list(value, "provider.quantizations")'," unknown = [item for item in items if item not in QUANTIZATION_VALUES]"," if unknown:",' raise GatewayConfigurationError("unsupported provider.quantizations value: " + ", ".join(unknown))'," return items","","","def default_policy_root() -> Path:"," here = Path(__file__).resolve()"," for candidate in here.parents[:8]:"," if (candidate / POLICY_FILE_NAME).is_file():"," return candidate"," cwd = Path.cwd()"," if (cwd / POLICY_FILE_NAME).is_file():"," return cwd"," return here.parents[2] if len(here.parents) > 2 else cwd","","","def load_policy_file(root: Path) -> object:"," path = root / POLICY_FILE_NAME"," try:",' return json.loads(path.read_text(encoding="utf-8"))'," except (OSError, json.JSONDecodeError, UnicodeError) as error:",' raise GatewayConfigurationError(f"Unable to read {POLICY_FILE_NAME}: {error}") from error',"","","def to_sdk_provider(provider: dict[str, Any] | None) -> dict[str, Any] | None:"," if not provider:"," return None"," mapped: dict[str, Any] = {}"," for key in (",' "allow_fallbacks",',' "order",',' "only",',' "ignore",',' "sort",',' "quantizations",',' "require_parameters",',' "zdr",',' "data_collection",',' "enforce_distillable_text",',' "max_price",',' "preferred_min_throughput",',' "preferred_max_latency",'," ):"," value = provider.get(key)"," if value not in (None, [], {}):"," mapped[key] = value"," return mapped or None","","","def _parse_policy(value: object) -> dict[str, Any]:"," if not isinstance(value, dict):",' raise GatewayConfigurationError("gateway.policy.json must be a JSON object.")',' _reject_unknown(value, ROOT_KEYS, "gateway.policy.json")',' models = None if "models" not in value else _string_list(value.get("models"), "models")',' raw_provider = value.get("provider") if "provider" in value else None'," provider: dict[str, Any] | None"," if raw_provider is None:"," provider = None"," else:"," if not isinstance(raw_provider, dict):",' raise GatewayConfigurationError("provider must be an object.")',' _reject_unknown(raw_provider, PROVIDER_KEYS, "provider")',' data_collection = raw_provider.get("data_collection")'," if data_collection is not None and data_collection not in DATA_COLLECTION_VALUES:",' raise GatewayConfigurationError("provider.data_collection must be allow or deny.")',' only = None if raw_provider.get("only") is None else _string_list(raw_provider.get("only"), "provider.only")',' ignore = None if raw_provider.get("ignore") is None else _string_list(raw_provider.get("ignore"), "provider.ignore")',' order = None if raw_provider.get("order") is None else _string_list(raw_provider.get("order"), "provider.order")'," provider = {",' "allow_fallbacks": _optional_bool(raw_provider.get("allow_fallbacks"), "provider.allow_fallbacks"),',' "require_parameters": _optional_bool(raw_provider.get("require_parameters"), "provider.require_parameters"),',' "zdr": _optional_bool(raw_provider.get("zdr"), "provider.zdr"),',' "enforce_distillable_text": _optional_bool(raw_provider.get("enforce_distillable_text"), "provider.enforce_distillable_text"),',' "data_collection": data_collection,',' "only": only,',' "ignore": ignore,',' "order": order,',' "sort": _sort(raw_provider.get("sort")),',' "quantizations": _quantizations(raw_provider.get("quantizations")),',' "max_price": _max_price(raw_provider.get("max_price")),',' "preferred_min_throughput": _percentile_or_number(raw_provider.get("preferred_min_throughput"), "provider.preferred_min_throughput"),',' "preferred_max_latency": _percentile_or_number(raw_provider.get("preferred_max_latency"), "provider.preferred_max_latency"),'," }",' timeout_ms = value.get("timeout_ms")'," return {",' "models": models,',' "provider": provider,',' "timeout_ms": None if timeout_ms is None else _policy_int(timeout_ms, "timeout_ms"),',' "http_referer": _optional_string(value.get("http_referer"), "http_referer"),',' "x_open_router_title": _optional_string(value.get("x_open_router_title"), "x_open_router_title"),'," }","","","def load_gateway_config(env: dict[str, str] | None = None, policy: object | None = None, root: Path | None = None) -> GatewayConfig:"," try:"," environ = env if env is not None else os.environ"," payload = _parse_policy(policy if policy is not None else load_policy_file(root or default_policy_root()))",' api_key = (environ.get("OPENROUTER_API_KEY") or "").strip()',' model = (environ.get("OPENROUTER_MODEL") or "").strip()'," if not api_key:",' raise GatewayConfigurationError("OPENROUTER_API_KEY is required before any OpenRouter request.")'," if not model:",' raise GatewayConfigurationError("OPENROUTER_MODEL is required. Choose a model slug; this project does not default one.")',' models = payload.get("models") or []'," if model in models:",' raise GatewayConfigurationError("models fallbacks must not repeat OPENROUTER_MODEL.")',' provider = payload.get("provider")',' only = (provider or {}).get("only") or []',' ignore = (provider or {}).get("ignore") or []',' order = (provider or {}).get("order") or []'," overlap = sorted(set(only) & set(ignore))"," if overlap:",' raise GatewayConfigurationError("provider.only and provider.ignore overlap: " + ", ".join(overlap))'," ordered_ignored = sorted(set(order) & set(ignore))"," if ordered_ignored:",' raise GatewayConfigurationError("provider.order includes ignored providers: " + ", ".join(ordered_ignored))',' if provider and provider.get("zdr") is True and provider.get("data_collection") == "allow":',' raise GatewayConfigurationError("provider.zdr=true cannot be combined with data_collection=allow.")',' timeout_raw = (environ.get("OPENROUTER_TIMEOUT_MS") or "").strip()',' timeout_ms = _env_int(timeout_raw, "OPENROUTER_TIMEOUT_MS") if timeout_raw else (',' payload["timeout_ms"] if payload.get("timeout_ms") is not None else DEFAULT_TIMEOUT_MS'," )",' http_referer = (environ.get("OPENROUTER_HTTP_REFERER") or "").strip() or payload.get("http_referer")',' title = (environ.get("OPENROUTER_X_OPEN_ROUTER_TITLE") or "").strip() or payload.get("x_open_router_title")'," return GatewayConfig("," api_key=api_key,"," model=model,"," models=models,"," timeout_ms=timeout_ms,"," http_referer=http_referer or None,"," x_open_router_title=title or None,"," provider=to_sdk_provider(provider),"," )"," except GatewayConfigurationError:"," raise"," except (AttributeError, TypeError, ValueError, KeyError) as error:",' raise GatewayConfigurationError(f"Invalid gateway policy: {error}") from error'])}function ea(){return h(["from __future__ import annotations","","from typing import Any, Iterator","","import httpx","from openrouter import OpenRouter","from openrouter.utils.retries import BackoffStrategy, RetryConfig","","from .config import GatewayConfig, load_gateway_config","from .errors import GatewayConfigurationError, to_gateway_upstream_error","","NO_RETRIES = RetryConfig(",' "none",'," BackoffStrategy(initial_interval=0, max_interval=0, exponent=1.0, max_elapsed_time=0),"," False,",")","","","def _text_from_content(value: object) -> str:"," if isinstance(value, str):"," return value"," if isinstance(value, list):",' return "".join(_text_from_content(item) for item in value)',' text = getattr(value, "content", None)'," if text is not None and text is not value:"," return _text_from_content(text)",' delta = getattr(value, "delta", None)'," if delta is not None:",' return _text_from_content(getattr(delta, "content", ""))'," if isinstance(value, dict):",' return _text_from_content(value.get("content") or value.get("text") or "")',' return "" if value is None else str(value)',"","","def _release_stream(stream: object) -> None:",' if stream is None or getattr(stream, "_gateway_released", False):'," return"," try:",' object.__setattr__(stream, "_gateway_released", True)'," except Exception: # noqa: BLE001 - release flag is best-effort"," pass",' generator = getattr(stream, "generator", None)',' iterator_close = getattr(generator, "close", None) if generator is not None else None'," if callable(iterator_close):"," try:"," iterator_close()"," except Exception: # noqa: BLE001 - cleanup must not hide the original error"," pass"," return",' closer = getattr(stream, "close", None)'," if callable(closer):"," try:"," closer()"," except Exception: # noqa: BLE001 - cleanup must not hide the original error"," pass"," return",' cancel = getattr(stream, "cancel", None)'," if callable(cancel):"," try:"," cancel()"," except Exception: # noqa: BLE001 - cleanup must not hide the original error"," pass","","","def _is_timeout(error: BaseException) -> bool:"," return isinstance(error, (TimeoutError, httpx.TimeoutException, httpx.ReadTimeout, httpx.ConnectTimeout))","","","class OpenRouterModelGateway:"," def __init__(self, config: GatewayConfig | None = None, client: httpx.Client | None = None) -> None:"," self._config = config or load_gateway_config()"," self._closed = False"," kwargs: dict[str, Any] = {",' "api_key": self._config.api_key,',' "http_referer": self._config.http_referer,',' "x_open_router_title": self._config.x_open_router_title,',' "timeout_ms": self._config.timeout_ms,',' "retry_config": NO_RETRIES,'," }"," if client is not None:",' kwargs["client"] = client'," self._sdk = OpenRouter(**kwargs)",""," def __enter__(self) -> OpenRouterModelGateway:"," return self",""," def __exit__(self, exc_type, exc, tb) -> None:"," self.close()",""," def close(self) -> None:"," if self._closed:"," return"," self._closed = True",' closer = getattr(self._sdk, "close", None)'," if callable(closer):"," closer()"," return",' exit_fn = getattr(self._sdk, "__exit__", None)'," if callable(exit_fn):"," exit_fn(None, None, None)",""," def _assert_open(self) -> None:"," if self._closed:",' raise GatewayConfigurationError("OpenRouterModelGateway is closed.")',""," def _normalize(self, error: BaseException) -> BaseException:"," if isinstance(error, GatewayConfigurationError) or _is_timeout(error):"," return error"," return to_gateway_upstream_error(error)",""," def generate(self, messages: list[dict[str, str]]) -> str:"," self._assert_open()"," if not messages:",' raise GatewayConfigurationError("messages must not be empty.")'," try:"," result = self._sdk.chat.send("," messages=messages,"," model=self._config.model,"," models=self._config.models or None,"," provider=self._config.provider,"," stream=False,"," timeout_ms=self._config.timeout_ms,"," retries=NO_RETRIES,"," )",' choices = getattr(result, "choices", None) or []',' message = getattr(choices[0], "message", None) if choices else None',' return _text_from_content(getattr(message, "content", ""))'," except GatewayConfigurationError:"," raise"," except Exception as error: # noqa: BLE001 - normalize upstream SDK errors"," raised = self._normalize(error)"," if raised is error:"," raise"," raise raised from error",""," def stream(self, messages: list[dict[str, str]]) -> Iterator[str]:"," self._assert_open()"," if not messages:",' raise GatewayConfigurationError("messages must not be empty.")'," opened: object | None = None"," try:"," opened = self._sdk.chat.send("," messages=messages,"," model=self._config.model,"," models=self._config.models or None,"," provider=self._config.provider,"," stream=True,"," timeout_ms=self._config.timeout_ms,"," retries=NO_RETRIES,"," )"," for event in opened:"," text = _text_from_content(event)"," if not text:",' choices = getattr(event, "choices", None) or []'," if choices:"," text = _text_from_content(choices[0])"," if text:"," yield text"," except GatewayConfigurationError:"," raise"," except Exception as error: # noqa: BLE001 - normalize open and iteration errors"," raised = self._normalize(error)"," if raised is error:"," raise"," raise raised from error"," finally:"," _release_stream(opened)"])}function ta(){return h(["from __future__ import annotations","","import json","import os","import sys","","import httpx","","from .config import load_gateway_config","from .errors import GatewayConfigurationError, map_upstream_error","from .gateway import OpenRouterModelGateway","","","def _prompt() -> str:",' args = [arg for arg in sys.argv[1:] if arg != "--smoke"]',' return " ".join(args).strip() or "Reply with a short acknowledgement."',"","",'def _chat_completion_payload(content: str, model: str = "openrouter/test-model") -> dict:'," return {",' "id": "smoke-non-stream",',' "created": 1,',' "model": model,',' "object": "chat.completion",',' "system_fingerprint": None,',' "choices": [{"finish_reason": "stop", "index": 0, "message": {"role": "assistant", "content": content}}],'," }","","","def _chat_stream_sse(parts: list[str]) -> bytes:"," events = []"," for index, content in enumerate(parts):"," payload = json.dumps("," {",' "id": "smoke-stream",',' "created": 1,',' "model": "openrouter/test-model",',' "object": "chat.completion.chunk",',' "choices": ['," {",' "delta": {"content": content},',' "finish_reason": "stop" if index == len(parts) - 1 else None,',' "index": 0,'," }"," ],"," }"," )",' events.append(f"data: {payload}\\n\\n")',' events.append("data: [DONE]\\n\\n")',' return "".join(events).encode("utf-8")',"","","def _smoke_handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream", "x-request-id": "smoke-stream"}, content=_chat_stream_sse(["smoke"]))'," parsed = json.loads(body) if body else {}"," return httpx.Response("," 200,",' headers={"content-type": "application/json", "x-request-id": "smoke-id"},',' json=_chat_completion_payload("smoke-ok", parsed.get("model") or "openrouter/test-model"),'," )","","","def run_smoke() -> None:",' os.environ.setdefault("OPENROUTER_API_KEY", "test-openrouter-key")',' os.environ.setdefault("OPENROUTER_MODEL", "openrouter/test-model")'," transport = httpx.MockTransport(_smoke_handler)"," with httpx.Client(transport=transport) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(), client=http_client) as gateway:",' generated = gateway.generate([{"role": "user", "content": "ping"}])',' streamed = "".join(gateway.stream([{"role": "user", "content": "ping"}]))',' sys.stdout.write(f"{generated}:{streamed}\\n")',"","","def main() -> None:",' if "--smoke" in sys.argv:'," run_smoke()"," return"," try:"," with OpenRouterModelGateway() as gateway:"," wrote = False",' for chunk in gateway.stream([{"role": "user", "content": _prompt()}]):'," sys.stdout.write(chunk)"," wrote = True"," if not wrote:",' sys.stdout.write(gateway.generate([{"role": "user", "content": _prompt()}]))',' sys.stdout.write("\\n")'," except GatewayConfigurationError as error:",' sys.stderr.write(str(error) + "\\n")'," raise SystemExit(1) from error"," except Exception as error: # noqa: BLE001 - CLI boundary"," mapped = map_upstream_error(error)",' sys.stderr.write(mapped.message + "\\n")'," raise SystemExit(1) from error","","",'if __name__ == "__main__":'," main()"])}function ra(){return h(["from .config import load_gateway_config, to_sdk_provider","from .errors import GatewayConfigurationError, GatewayUpstreamError, map_upstream_error","from .gateway import OpenRouterModelGateway","from .redact import redact_secret_shaped_values","","__all__ = [",' "GatewayConfigurationError",',' "GatewayUpstreamError",',' "OpenRouterModelGateway",',' "load_gateway_config",',' "map_upstream_error",',' "redact_secret_shaped_values",',' "to_sdk_provider",',"]"])}function oa(){return h(["from model_gateway.main import main","",'if __name__ == "__main__":'," main()"])}function na(){return h(["from __future__ import annotations","","import json","import os","import tempfile","import unittest","from pathlib import Path","","import httpx","","from model_gateway.config import load_gateway_config, load_policy_file, to_sdk_provider","from model_gateway.errors import GatewayConfigurationError, GatewayUpstreamError, map_upstream_error","from model_gateway.gateway import OpenRouterModelGateway","from model_gateway.redact import redact_secret_shaped_values","","ROOT = Path(__file__).resolve().parents[1]",`PARITY_CASES = json.loads(${JSON.stringify(JSON.stringify(it))})`,"","",'def chat_completion_payload(content: str, model: str = "openrouter/test-model") -> dict:'," return {",' "id": "gen-1",',' "created": 1,',' "model": model,',' "object": "chat.completion",',' "system_fingerprint": None,',' "choices": [{"finish_reason": "stop", "index": 0, "message": {"role": "assistant", "content": content}}],'," }","","","def chat_stream_sse(parts: list[str]) -> bytes:"," events = []"," for index, content in enumerate(parts):"," payload = json.dumps("," {",' "id": "stream-1",',' "created": 1,',' "model": "openrouter/test-model",',' "object": "chat.completion.chunk",',' "choices": ['," {",' "delta": {"content": content},',' "finish_reason": "stop" if index == len(parts) - 1 else None,',' "index": 0,'," }"," ],"," }"," )",' events.append(f"data: {payload}\\n\\n")',' events.append("data: [DONE]\\n\\n")',' return "".join(events).encode("utf-8")',"","","class GatewayTests(unittest.TestCase):"," def setUp(self) -> None:",' os.environ["OPENROUTER_API_KEY"] = "sk-or-test"',' os.environ["OPENROUTER_MODEL"] = "openrouter/test-model"',""," def tearDown(self) -> None:",' os.environ.pop("OPENROUTER_TIMEOUT_MS", None)',""," def test_redacts_secrets(self) -> None:",' os.environ["OPENROUTER_API_KEY"] = "sk-or-live-secret"',' raw = "Authorization: Bearer sk-or-live-secret https://example.test/v1?api_key=sk-or-live-secret"'," redacted = redact_secret_shaped_values(raw)",' self.assertNotIn("sk-or-live-secret", redacted)',' self.assertIn("[redacted]", redacted)',""," def test_config_fails_before_network(self) -> None:",' os.environ.pop("OPENROUTER_API_KEY", None)'," with self.assertRaises(GatewayConfigurationError):"," load_gateway_config(root=ROOT)",""," def test_overlapping_provider_lists_fail(self) -> None:",' with self.assertRaisesRegex(GatewayConfigurationError, "overlap"):',' load_gateway_config(policy={"provider": {"only": ["openai"], "ignore": ["openai"]}})',""," def test_python_provider_field_names(self) -> None:"," mapped = to_sdk_provider("," {",' "allow_fallbacks": False,',' "zdr": True,',' "data_collection": "deny",',' "sort": "latency",',' "order": ["anthropic"],'," }"," )"," self.assertEqual("," mapped,"," {",' "allow_fallbacks": False,',' "zdr": True,',' "data_collection": "deny",',' "sort": "latency",',' "order": ["anthropic"],'," },"," )",""," def test_generate_uses_injected_transport(self) -> None:"," recorded: list[str] = []",""," def handler(request: httpx.Request) -> httpx.Response:",' recorded.append(request.content.decode("utf-8"))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," config = load_gateway_config(",' policy={"provider": {"zdr": True, "data_collection": "deny"}, "models": ["openrouter/fallback"]}'," )"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=config, client=http_client) as gateway:",' text = gateway.generate([{"role": "user", "content": "hi"}])',' self.assertEqual(text, "ok")'," payload = json.loads(recorded[0])",' self.assertEqual(payload["model"], "openrouter/test-model")',' self.assertEqual(payload.get("models"), ["openrouter/fallback"])',' self.assertTrue(payload["provider"]["zdr"])',' self.assertEqual(payload["provider"]["data_collection"], "deny")',""," def test_stream_closes_event_stream(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel", "lo"]))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' text = "".join(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertTrue("hel" in text or "hello" in text)',""," def test_maps_status_and_request_id(self) -> None:"," response = httpx.Response("," 429,",' headers={"authorization": "Bearer sk-or-secret", "x-request-id": "req-123", "retry-after": "2"},',' text="Authorization: Bearer sk-or-secret",'," )",""," class FakeError(Exception):"," def __init__(self) -> None:",' super().__init__("Authorization: Bearer sk-or-secret failed")'," self.status_code = 429"," self.headers = response.headers",""," mapped = map_upstream_error(FakeError())"," self.assertEqual(mapped.status_code, 429)",' self.assertEqual(mapped.request_id, "req-123")',' self.assertEqual(mapped.retry_after, "2")',' self.assertNotIn("sk-or-secret", mapped.message)',' self.assertEqual(mapped.safe_headers.get("authorization") or mapped.safe_headers.get("Authorization"), "[redacted]")',""," def test_timeout_from_env(self) -> None:",' os.environ["OPENROUTER_TIMEOUT_MS"] = "1500"'," config = load_gateway_config(policy={})"," self.assertEqual(config.timeout_ms, 1500)",""," def test_policy_file_has_no_secrets(self) -> None:",' policy = (ROOT / "gateway.policy.json").read_text(encoding="utf-8")',' self.assertNotIn("sk-", policy)',' self.assertNotIn("OPENROUTER_API_KEY", policy)',""," def test_rejects_invalid_policy_categories(self) -> None:"," cases = [",' ([], "JSON object"),',' ({"widget": True}, "unknown field"),',' ({"models": "openai/gpt"}, "models must be an array"),',' ({"models": [" "]}, "non-empty string"),',' ({"models": ["a", "a"]}, "duplicates"),',' ({"models": ["openrouter/test-model"]}, "repeat OPENROUTER_MODEL"),',' ({"provider": "openai"}, "provider must be an object"),',' ({"provider": {"fallbacks": True}}, "unknown field"),',' ({"provider": {"allow_fallbacks": "yes"}}, "boolean"),',' ({"provider": {"require_parameters": 1}}, "boolean"),',' ({"provider": {"zdr": "true"}}, "boolean"),',' ({"provider": {"only": ["openai"], "ignore": ["openai"]}}, "overlap"),',' ({"provider": {"order": ["openai"], "ignore": ["openai"]}}, "ignored providers"),',' ({"provider": {"data_collection": "never"}}, "allow or deny"),',' ({"provider": {"sort": "cheap"}}, "sort"),',' ({"provider": {"sort": {"by": "price", "partition": "all"}}}, "partition"),',' ({"provider": {"quantizations": ["int5"]}}, "quantizations"),',' ({"provider": {"max_price": {"prompt": 1}}}, "numeric string"),',' ({"provider": {"max_price": {"tokens": "1"}}}, "unknown field"),',' ({"provider": {"max_price": {"prompt": "-1"}}}, "numeric string"),',' ({"provider": {"max_price": {"prompt": "not-a-price"}}}, "numeric string"),',' ({"provider": {"max_price": {"prompt": "9" * 400}}}, "numeric string"),',' ({"provider": {"preferred_max_latency": {"p95": 1}}}, "unknown field"),',' ({"provider": {"preferred_min_throughput": {"p50": -1}}}, "non-negative"),',' ({"timeout_ms": 30.5}, "positive integer"),',' ({"timeout_ms": 0}, "positive integer"),',' ({"timeout_ms": 600001}, "positive integer"),',' ({"timeout_ms": "30000"}, "positive integer"),',' ({"http_referer": 1}, "string"),',' ({"provider": {"zdr": True, "data_collection": "allow"}}, "zdr"),'," ]"," for policy, needle in cases:"," with self.subTest(needle=needle, policy=policy):"," with self.assertRaises(GatewayConfigurationError) as raised:"," load_gateway_config(policy=policy)"," self.assertIn(needle, str(raised.exception))"," self.assertNotIsInstance(raised.exception, (AttributeError, TypeError, ValueError))",""," def test_accepts_official_provider_policy_boundaries(self) -> None:"," config = load_gateway_config("," policy={",' "models": ["openrouter/fallback"],',' "timeout_ms": 1,',' "http_referer": "https://example.test",',' "x_open_router_title": "gateway",',' "provider": {',' "allow_fallbacks": False,',' "require_parameters": True,',' "zdr": True,',' "data_collection": "deny",',' "enforce_distillable_text": True,',' "only": ["anthropic"],',' "ignore": ["openai"],',' "order": ["anthropic"],',' "sort": {"by": "exacto", "partition": "none"},',' "quantizations": ["fp16"],',' "max_price": {"prompt": "0", "completion": "1.5", "request": "2", "image": "3", "audio": "4"},',' "preferred_min_throughput": {"p50": 0, "p75": 1, "p90": 2, "p99": 3},',' "preferred_max_latency": 1.5,'," },"," }"," )"," self.assertEqual(config.timeout_ms, 1)"," mapped = to_sdk_provider(config.provider)",' self.assertEqual(mapped["enforce_distillable_text"], True)',' self.assertEqual(mapped["sort"], {"by": "exacto", "partition": "none"})',' self.assertEqual(mapped["max_price"]["prompt"], "0")',' upper = load_gateway_config(policy={"timeout_ms": 600000})'," self.assertEqual(upper.timeout_ms, 600000)",' tiny = load_gateway_config(policy={"provider": {"max_price": {"prompt": "0.000001"}}})',' self.assertEqual(to_sdk_provider(tiny.provider)["max_price"]["prompt"], "0.000001")',""," def test_policy_parity_corpus_executes_generated_validator(self) -> None:"," for case in PARITY_CASES:",' with self.subTest(id=case["id"]):',' if case["decision"] == "reject":'," with self.assertRaises(GatewayConfigurationError) as raised:",' load_gateway_config(policy=case["python"])',' needle = case.get("needle")'," if needle:"," self.assertIn(needle, str(raised.exception))"," self.assertNotIsInstance(raised.exception, (AttributeError, TypeError, ValueError))"," else:",' config = load_gateway_config(policy=case["python"])'," mapped = to_sdk_provider(config.provider)",' if case["id"] == "max-price-zero":',' self.assertEqual(mapped["max_price"]["prompt"], "0")',' if case["id"] == "max-price-micro":',' self.assertEqual(mapped["max_price"]["prompt"], "0.000001")',' if case["id"] == "max-price-positive":',' self.assertEqual(mapped["max_price"]["completion"], "1.5")',""," def test_finite_max_price_strings_are_preserved_or_rejected(self) -> None:"," accepted = load_gateway_config(",' policy={"provider": {"max_price": {"prompt": "0", "completion": "0.000001", "request": "12.5"}}}'," )",' mapped = to_sdk_provider(accepted.provider)["max_price"]',' self.assertEqual(mapped["prompt"], "0")',' self.assertEqual(mapped["completion"], "0.000001")',' self.assertEqual(mapped["request"], "12.5")',' for value in ("-1", "not-a-price", "9" * 400, "1e6", "+1", "inf", "nan"):'," with self.subTest(value=value):"," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"max_price": {"prompt": value}}})'," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"max_price": {"tokens": "1"}}})',""," def test_rejects_non_finite_numeric_policy_values(self) -> None:"," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"preferred_max_latency": float("inf")}})'," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"preferred_min_throughput": float("nan")}})'," with self.assertRaises(GatewayConfigurationError):",' load_gateway_config(policy={"provider": {"preferred_max_latency": {"p50": float("-inf")}}})',""," def test_missing_policy_file_is_configuration_error(self) -> None:"," with tempfile.TemporaryDirectory() as directory:"," with self.assertRaises(GatewayConfigurationError) as raised:"," load_policy_file(Path(directory))",' self.assertIn("Unable to read", str(raised.exception))',""," def test_malformed_policy_json_is_configuration_error(self) -> None:"," with tempfile.TemporaryDirectory() as directory:"," root = Path(directory)",' (root / "gateway.policy.json").write_text("{", encoding="utf-8")'," with self.assertRaises(GatewayConfigurationError):"," load_policy_file(root)",""," def test_empty_messages_fail_before_transport(self) -> None:",' called = {"value": False}',""," def handler(request: httpx.Request) -> httpx.Response:",' called["value"] = True',' return httpx.Response(200, json=chat_completion_payload("nope"))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises(GatewayConfigurationError):"," gateway.generate([])"," with self.assertRaises(GatewayConfigurationError):"," next(gateway.stream([]))",' self.assertFalse(called["value"])',""," def test_empty_successful_completion_returns_empty_string(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, json=chat_completion_payload(""))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "")',""," def test_close_is_idempotent(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, json=chat_completion_payload("ok"))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," gateway = OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client)"," gateway.close()"," gateway.close()"," with self.assertRaises(GatewayConfigurationError):",' gateway.generate([{"role": "user", "content": "hi"}])',""," def test_stream_open_failure_is_redacted_upstream_error(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:"," return httpx.Response("," 401,",' headers={"authorization": "Bearer sk-or-secret", "x-request-id": "req-open", "retry-after": "7", "content-type": "application/json"},',' json={"error": {"message": "Authorization: Bearer sk-or-secret"}},'," )",""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises(GatewayUpstreamError) as raised:",' next(gateway.stream([{"role": "user", "content": "hi"}]))'," self.assertEqual(raised.exception.status_code, 401)",' self.assertEqual(raised.exception.request_id, "req-open")',' self.assertEqual(raised.exception.retry_after, "7")',' self.assertNotIn("sk-or-secret", str(raised.exception))',""," def test_mid_stream_failure_is_normalized_and_cleaned_up(self) -> None:",' closed = {"value": False}',""," def body():"," try:",' yield chat_stream_sse(["hel"]).replace(b"data: [DONE]\\n\\n", b"")',' raise RuntimeError("Authorization: Bearer sk-or-secret mid-stream")'," finally:",' closed["value"] = True',""," def handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=body())',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises((GatewayUpstreamError, RuntimeError)) as raised:",' list(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertNotIn("sk-or-secret", str(raised.exception))',' self.assertTrue(closed["value"])',""," def test_consumer_early_termination_closes_upstream_stream(self) -> None:"," def handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel"]))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' iterator = gateway.stream([{"role": "user", "content": "hi"}])'," chunk = next(iterator)",' self.assertIn("hel", chunk)'," iterator.close()",' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")',""," def _track_event_stream_cleanup(self):"," from openrouter.utils.eventstreaming import EventStream","",' counts = {"return": 0, "close": 0}'," original_init = EventStream.__init__"," original_close = EventStream.close",""," def tracked_init(self, *args, **kwargs):"," original_init(self, *args, **kwargs)"," inner = self.generator",""," class Probe:"," def __iter__(self_inner):"," return self_inner",""," def __next__(self_inner):"," return next(inner)",""," def close(self_inner):",' counts["return"] += 1'," return inner.close()",""," self.generator = Probe()",""," def tracked_close(self):",' counts["close"] += 1'," return original_close(self)",""," EventStream.__init__ = tracked_init"," EventStream.close = tracked_close"," return EventStream, original_init, original_close, counts",""," def test_normal_stream_completion_releases_iterator_once(self) -> None:"," EventStream, original_init, original_close, counts = self._track_event_stream_cleanup()",""," def handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel"]))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," try:"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' self.assertIn("hel", "".join(gateway.stream([{"role": "user", "content": "hi"}])))',' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")'," finally:"," EventStream.__init__ = original_init"," EventStream.close = original_close",' self.assertEqual(counts["return"], 1)',' self.assertEqual(counts["close"], 0)',""," def test_early_generator_close_releases_iterator_once(self) -> None:"," EventStream, original_init, original_close, counts = self._track_event_stream_cleanup()",""," def handler(request: httpx.Request) -> httpx.Response:",' body = request.content.decode("utf-8")',` if '"stream": true' in body or '"stream":true' in body:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel", "lo"]))',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," try:"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' iterator = gateway.stream([{"role": "user", "content": "hi"}])',' self.assertIn("hel", next(iterator))'," iterator.close()"," iterator.close()",' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")'," finally:"," EventStream.__init__ = original_init"," EventStream.close = original_close",' self.assertEqual(counts["return"], 1)',' self.assertEqual(counts["close"], 0)',""," def test_mid_stream_error_releases_iterator_once(self) -> None:"," EventStream, original_init, original_close, counts = self._track_event_stream_cleanup()",""," def body():",' yield chat_stream_sse(["hel"]).replace(b"data: [DONE]\\n\\n", b"")',' raise RuntimeError("Authorization: Bearer sk-or-secret mid-stream")',""," def handler(request: httpx.Request) -> httpx.Response:",' body_text = request.content.decode("utf-8")',` if '"stream": true' in body_text or '"stream":true' in body_text:`,' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=body())',' return httpx.Response(200, json=chat_completion_payload("ok"))',""," try:"," with httpx.Client(transport=httpx.MockTransport(handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises((GatewayUpstreamError, RuntimeError)) as raised:",' list(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertNotIn("sk-or-secret", str(raised.exception))',' self.assertEqual(gateway.generate([{"role": "user", "content": "hi"}]), "ok")'," finally:"," EventStream.__init__ = original_init"," EventStream.close = original_close",' self.assertEqual(counts["return"], 1)',' self.assertEqual(counts["close"], 0)',""," def test_cleanup_failure_does_not_mask_upstream_or_success(self) -> None:"," from openrouter.utils.eventstreaming import EventStream",""," original_init = EventStream.__init__",""," def tracked_init(self, *args, **kwargs):"," original_init(self, *args, **kwargs)"," inner = self.generator",""," class Probe:"," def __iter__(self_inner):"," return self_inner",""," def __next__(self_inner):"," return next(inner)",""," def close(self_inner):"," inner.close()",' raise RuntimeError("cleanup-failed")',""," self.generator = Probe()",""," EventStream.__init__ = tracked_init"," try:"," def success_handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=chat_stream_sse(["hel"]))',""," def failing_body():",' yield chat_stream_sse(["hel"]).replace(b"data: [DONE]\\n\\n", b"")',' raise RuntimeError("Authorization: Bearer sk-or-secret mid-stream")',""," def fail_handler(request: httpx.Request) -> httpx.Response:",' return httpx.Response(200, headers={"content-type": "text/event-stream"}, content=failing_body())',""," with httpx.Client(transport=httpx.MockTransport(success_handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:",' self.assertIn("hel", "".join(gateway.stream([{"role": "user", "content": "hi"}])))'," with httpx.Client(transport=httpx.MockTransport(fail_handler)) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises((GatewayUpstreamError, RuntimeError)) as raised:",' list(gateway.stream([{"role": "user", "content": "hi"}]))',' self.assertNotIn("sk-or-secret", str(raised.exception))',' self.assertNotIn("cleanup-failed", str(raised.exception))'," finally:"," EventStream.__init__ = original_init",""," def test_timeout_errors_remain_recognizable(self) -> None:"," class TimeoutTransport(httpx.BaseTransport):"," def handle_request(self, request: httpx.Request) -> httpx.Response:",' raise httpx.TimeoutException("Request timed out")',""," with httpx.Client(transport=TimeoutTransport()) as http_client:"," with OpenRouterModelGateway(config=load_gateway_config(policy={}), client=http_client) as gateway:"," with self.assertRaises(httpx.TimeoutException):",' gateway.generate([{"role": "user", "content": "hi"}])'," with self.assertRaises(httpx.TimeoutException):",' next(gateway.stream([{"role": "user", "content": "hi"}]))',""," def test_policy_root_does_not_require_process_cwd(self) -> None:"," from model_gateway.config import default_policy_root",""," resolved = default_policy_root()",' self.assertTrue((resolved / "gateway.policy.json").is_file())',"","",'if __name__ == "__main__":'," unittest.main()"])}async function aa(e){let t=Yr(e.projectName);await e$1(x.join(e.projectPath,"pyproject.toml"),Bn(t)),await e$1(x.join(e.projectPath,de),zn()),await e$1(x.join(e.projectPath,".env.example"),Yn()),await e$1(x.join(e.projectPath,".gitignore"),st),await e$1(x.join(e.projectPath,"README.md"),Xn(e.projectName)),await e$1(x.join(e.projectPath,"main.py"),oa()),await e$1(x.join(e.projectPath,"src","model_gateway","__init__.py"),ra()),await e$1(x.join(e.projectPath,"src","model_gateway","redact.py"),Jn()),await e$1(x.join(e.projectPath,"src","model_gateway","errors.py"),Hn()),await e$1(x.join(e.projectPath,"src","model_gateway","port.py"),Zn()),await e$1(x.join(e.projectPath,"src","model_gateway","config.py"),Qn()),await e$1(x.join(e.projectPath,"src","model_gateway","gateway.py"),ea()),await e$1(x.join(e.projectPath,"src","model_gateway","main.py"),ta()),await e$1(x.join(e.projectPath,"tests","test_gateway.py"),na());}var ro={id:"openrouter-python",gatewayId:"openrouter",gatewayName:"OpenRouter",kitId:"gateway.openrouter.python",aliases:["gateway.openrouter.python","openrouter.python","gateway.openrouter.py","openrouter-python"],label:"OpenRouter \xB7 Python",runtime:"python",requiredEnvironment:["OPENROUTER_API_KEY","OPENROUTER_MODEL"],versionBaselineId:"openrouter-python",capabilities:pe,lifecycle:{stages:lt},attach:"unsupported",generate:aa};var dt=rt("openrouter-typescript");if(!dt)throw new Error("Missing OpenRouter TypeScript gateway baseline.");var oo=ce(dt,"@openrouter/sdk"),sa=ce(dt,"typescript"),ia=ce(dt,"@types/node");function la(e){return `${JSON.stringify({name:e,version:"0.1.0",private:true,type:"module",engines:{node:">=20"},scripts:{build:"tsc --pretty false",typecheck:"tsc --noEmit --pretty false",test:"tsc --pretty false && node --test dist/tests/gateway.test.js",start:"tsc --pretty false && node dist/src/main.js",smoke:"tsc --pretty false && node dist/src/main.js --smoke"},dependencies:{"@openrouter/sdk":oo},devDependencies:{"@types/node":ia,typescript:sa}},null,2)}
4734
4743
  `}function ca(){return `${JSON.stringify({compilerOptions:{target:"ES2022",lib:["ES2022","DOM"],module:"Node16",moduleResolution:"Node16",strict:true,esModuleInterop:true,skipLibCheck:true,types:["node"],outDir:"dist",rootDir:"."},include:["src/**/*.ts","tests/**/*.ts"]},null,2)}
4735
4744
  `}function da(){return `${JSON.stringify({models:[],provider:{allowFallbacks:null,order:null,only:null,ignore:null,sort:null,quantizations:null,requireParameters:null,zdr:null,dataCollection:null,enforceDistillableText:null,maxPrice:null,preferredMinThroughput:null,preferredMaxLatency:null},timeoutMs:3e4,httpReferer:null,appTitle:null},null,2)}
4736
- `}function pa(){return ct({attributionComment:["# Optional application attribution. These map to the official TypeScript SDK","# fields httpReferer and appTitle."],refererName:"OPENROUTER_HTTP_REFERER",titleName:"OPENROUTER_APP_TITLE"})}function ma(e){return h([`# ${e}`,"","This is a server-owned OpenRouter Model Gateway generated by Workspai.","It is an AI Gateway, not an agent runtime. OpenRouter owns provider and","model routing. Workspai owns generation, lifecycle, and verification.","","The official Client SDK is `@openrouter/sdk` `"+oo+"`.","A model identifier is runtime configuration, not a project kit.","","## Security","","- Keep `OPENROUTER_API_KEY` on the server. Do not put it in browser bundles,"," `VITE_`, `NEXT_PUBLIC_`, or other client-side environment prefixes.","- Diagnostics redact Authorization headers, API keys, URL secrets, and request bodies.","- Configuration is validated before any network request.","- This project does not disable TLS verification and does not enable SDK debug logging.","- Privacy and routing policy live in `gateway.policy.json` so they stay auditable.","- When a privacy or routing field is omitted, OpenRouter upstream defaults remain in"," effect. The SDK documents `dataCollection` default as `allow` when unset.","","## Configure","","1. Copy `.env.example` into your secret store or shell. Do not commit `.env`.","2. Set `OPENROUTER_API_KEY` and `OPENROUTER_MODEL`.","3. Edit `gateway.policy.json` for official TypeScript SDK provider controls:"," fallbacks, order/allow/deny, sort (`price`/`throughput`/`latency`/`exacto`,"," optional `{ by, partition }` where `partition` is `model` or `none`),"," quantizations, requireParameters, ZDR, dataCollection, enforceDistillableText,"," maxPrice, and preferred throughput or latency. Unknown fields are rejected.","",'Contradictory policy fails before a request is sent. ZDR and `dataCollection: "deny"`',"are never silently dropped. Application-side retries are disabled so they cannot","duplicate billable requests; OpenRouter still applies its own provider fallbacks","when `allowFallbacks` is not false. An empty successful completion returns an empty","string. Timeout must be a bounded positive integer (1 to 600000 ms). `maxPrice`","values must be numeric strings that stay finite after `Number(...)`; overflow-to-","infinity strings are rejected. Streaming cleanup prefers async iterator `return()`,","then SDK `close()`, then `cancel()`, and never invokes two of those on the same","stream. Pinned `@openrouter/sdk` `EventStream` extends `ReadableStream` and has no","`close()`; if a stream already errored, `cancel()` may not run.","","Capabilities such as tools, JSON schema, streaming, images, audio, and embeddings","vary by model and provider. This starter only claims chat generation and streaming.","","## Commands","","Run these from the project root after installing dependencies locally:","","```bash","npm install","npm test","npm run typecheck","npm run build","npm run smoke","npm start","```","","`npm test` and `npm run smoke` inject a fake transport. They do not call OpenRouter","and do not require `OPENROUTER_API_KEY`. `npm start` is the live entrypoint and","requires credentials.","","Workspace Run uses the same scripts: `init` -> `npm install`, `test` -> `npm test`,","`build` -> `npm run build`, `start` -> `npm start`.","","## Future composition","","An agent runtime can later consume this Model Gateway port. Do not copy these","templates into an Agent Framework kit; compose against the port instead."])}function ua(){return h(["export function redactSecretShapedValues(value: string): string {"," let output = value;"," const liveKey = process.env.OPENROUTER_API_KEY;"," if (liveKey && liveKey.length > 0) {"," output = output.split(liveKey).join('[redacted]');"," }"," return output",' .replace(/(?:sk-or-[A-Za-z0-9_-]+|sk-[A-Za-z0-9_-]{8,})/gi, "[redacted]")',' .replace(/Bearer\\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")',` .replace(/authorization\\s*[:=]\\s*["\\']?[^"\\'\\s]+/gi, "Authorization: [redacted]")`,' .replace(/([?&](?:api[_-]?key|token|secret|password)=)[^&\\s]+/gi, "$1[redacted]");',"}","","function isHeaderMap(value: Headers | Record<string, string>): value is Headers {",' return typeof (value as Headers).forEach === "function" && typeof (value as Headers).get === "function";',"}","","export function redactHeaders(headers: Headers | Record<string, string>): Record<string, string> {"," const out: Record<string, string> = {};"," const assign = (key: string, value: string): void => {",' out[key] = key.toLowerCase() === "authorization" ? "[redacted]" : redactSecretShapedValues(value);'," };"," if (isHeaderMap(headers)) {"," headers.forEach((value, key) => assign(key, value));"," return out;"," }"," for (const [key, value] of Object.entries(headers)) {"," assign(key, value);"," }"," return out;","}"])}function fa(){return h(["import { redactHeaders, redactSecretShapedValues } from './redact.js';","","export type GatewayError = {"," message: string;"," statusCode?: number;"," requestId?: string;"," retryAfter?: string;"," safeHeaders?: Record<string, string>;","};","","export class GatewayConfigurationError extends Error {"," constructor(message: string) {"," super(message);",' this.name = "GatewayConfigurationError";'," }","}","","export class GatewayUpstreamError extends Error {"," readonly statusCode?: number;"," readonly requestId?: string;"," readonly retryAfter?: string;"," constructor(mapped: GatewayError) {",' const suffix = mapped.statusCode ? ` (status ${mapped.statusCode})` : "";',' const request = mapped.requestId ? ` request ${mapped.requestId}` : "";'," super(`${mapped.message}${suffix}${request}`);",' this.name = "GatewayUpstreamError";'," this.statusCode = mapped.statusCode;"," this.requestId = mapped.requestId;"," this.retryAfter = mapped.retryAfter;"," }","}","","function isRecord(value: unknown): value is Record<string, unknown> {",' return Boolean(value) && typeof value === "object";',"}","","function readHeaderMap(value: unknown): Record<string, string> | undefined {",' if (!value || typeof value !== "object") return undefined;',' if (typeof (value as Headers).forEach === "function" && typeof (value as Headers).get === "function") {'," return redactHeaders(value as Headers);"," }"," const out: Record<string, string> = {};"," for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {",' if (typeof entry === "string") out[key] = entry;'," }"," return Object.keys(out).length > 0 ? redactHeaders(out) : undefined;","}","","function headerLookup(headers: Record<string, string> | undefined, name: string): string | undefined {"," if (!headers) return undefined;"," const wanted = name.toLowerCase();"," for (const [key, value] of Object.entries(headers)) {"," if (key.toLowerCase() === wanted) return value;"," }"," return undefined;","}","","function numericStatus(value: unknown): number | undefined {",' return typeof value === "number" && Number.isInteger(value) ? value : undefined;',"}","","export function mapUpstreamError(error: unknown): GatewayError {"," const record = isRecord(error) ? error : {};"," const cause = isRecord(record.cause) ? record.cause : {};"," const httpMeta = isRecord(record.httpMeta)"," ? record.httpMeta"," : isRecord(record.HttpMeta)"," ? record.HttpMeta"," : {};"," const response = isRecord(httpMeta.Response)"," ? httpMeta.Response"," : isRecord(httpMeta.response)"," ? httpMeta.response"," : {};"," const statusCode ="," numericStatus(record.statusCode) ??"," numericStatus(record.status) ??"," numericStatus(record.status_code) ??"," numericStatus(cause.statusCode) ??"," numericStatus(response.status);"," const headers ="," readHeaderMap(record.headers) ??"," readHeaderMap(cause.headers) ??"," readHeaderMap(response.headers);"," const requestId =",' headerLookup(headers, "x-request-id") ??',' headerLookup(headers, "x-openrouter-request-id") ??',' (typeof record.requestId === "string" ? record.requestId : undefined);'," const rawMessage = error instanceof Error ? error.message : String(error);"," return {"," message: redactSecretShapedValues(rawMessage),"," statusCode,"," requestId,",' retryAfter: headerLookup(headers, "retry-after"),'," safeHeaders: headers,"," };","}","","export function toGatewayUpstreamError(error: unknown): GatewayUpstreamError {"," if (error instanceof GatewayUpstreamError) return error;"," return new GatewayUpstreamError(mapUpstreamError(error));","}"])}function ga(){return h(["export type ChatTurn = {",' role: "system" | "user" | "assistant";'," content: string;","};","","export type GenerateInput = {"," messages: ChatTurn[];"," signal?: AbortSignal;","};","","export type GenerateResult = {"," text: string;"," model?: string;"," id?: string;","};","","export interface ModelGateway {"," generate(input: GenerateInput): Promise<GenerateResult>;"," stream(input: GenerateInput): AsyncGenerator<string, void, void>;"," close(): Promise<void>;","}"])}function _a(){let e=we.map(i=>`"${i}"`).join(" | "),t=ke.map(i=>`"${i}"`).join(" | "),r=Ee.map(i=>`"${i}"`).join(" | "),o=at.map(i=>`"${i}"`).join(" | "),a=we.map(i=>`"${i}"`).join(", "),s=ke.map(i=>`"${i}"`).join(", "),n=Ee.map(i=>`"${i}"`).join(", "),l=nt.map(i=>`"${i}"`).join(", "),c=ot.map(i=>`"${i}"`).join(", ");return h(["import { readFileSync } from 'node:fs';","import { dirname, join } from 'node:path';","import { fileURLToPath } from 'node:url';","import type { ProviderPreferences } from '@openrouter/sdk/models/providerpreferences.js';","import { GatewayConfigurationError } from './errors.js';","","export { GatewayConfigurationError };","",`export const POLICY_FILE_NAME = ${JSON.stringify(de)};`,`export const DEFAULT_TIMEOUT_MS = ${3e4};`,`export const MAX_TIMEOUT_MS = ${6e5};`,`export type ProviderSort = ${e};`,`export type SortPartition = ${t};`,`export type Quantization = ${r};`,`export type DataCollection = ${o};`,`const SORT_VALUES = new Set<string>([${a}]);`,`const PARTITION_VALUES = new Set<string>([${s}]);`,`const QUANTIZATION_VALUES = new Set<string>([${n}]);`,`const MAX_PRICE_KEYS = new Set<string>([${l}]);`,`const PERCENTILE_KEYS = new Set<string>([${c}]);`,'const ROOT_KEYS = new Set(["models", "provider", "timeoutMs", "httpReferer", "appTitle"]);',"const PROVIDER_KEYS = new Set([",' "allowFallbacks",',' "order",',' "only",',' "ignore",',' "sort",',' "quantizations",',' "requireParameters",',' "zdr",',' "dataCollection",',' "enforceDistillableText",',' "maxPrice",',' "preferredMinThroughput",',' "preferredMaxLatency",',"]);","","export type MaxPrice = {"," prompt?: string;"," completion?: string;"," request?: string;"," image?: string;"," audio?: string;","};","","export type PercentileCutoffs = {"," p50?: number | null;"," p75?: number | null;"," p90?: number | null;"," p99?: number | null;","};","","export type ProviderSortConfig = {"," by?: ProviderSort | null;"," partition?: SortPartition | null;","};","","export type TypeScriptProviderPolicy = {"," allowFallbacks?: boolean | null;"," order?: string[] | null;"," only?: string[] | null;"," ignore?: string[] | null;"," sort?: ProviderSort | ProviderSortConfig | null;"," quantizations?: Quantization[] | null;"," requireParameters?: boolean | null;"," zdr?: boolean | null;"," dataCollection?: DataCollection | null;"," enforceDistillableText?: boolean | null;"," maxPrice?: MaxPrice | null;"," preferredMinThroughput?: number | PercentileCutoffs | null;"," preferredMaxLatency?: number | PercentileCutoffs | null;","};","","export type GatewayPolicyFile = {"," models?: string[];"," provider?: TypeScriptProviderPolicy | null;"," timeoutMs?: number | null;"," httpReferer?: string | null;"," appTitle?: string | null;","};","","export type GatewayConfig = {"," apiKey: string;"," model: string;"," models: string[];"," timeoutMs: number;"," httpReferer?: string;"," appTitle?: string;"," provider?: TypeScriptProviderPolicy;","};","","function isRecord(value: unknown): value is Record<string, unknown> {",' return Boolean(value) && typeof value === "object" && !Array.isArray(value);',"}","","function rejectUnknownKeys(record: Record<string, unknown>, allowed: Set<string>, label: string): void {"," const unknown = Object.keys(record).filter((key) => !allowed.has(key));"," if (unknown.length > 0) {",' throw new GatewayConfigurationError(`${label} contains unknown field(s): ${unknown.sort().join(", ")}`);'," }","}","","function optionalBoolean(value: unknown, label: string): boolean | null | undefined {"," if (value === undefined || value === null) return value;",' if (typeof value !== "boolean") {'," throw new GatewayConfigurationError(`${label} must be a boolean.`);"," }"," return value;","}","","function optionalString(value: unknown, label: string): string | undefined {",' if (value === undefined || value === null || value === "") return undefined;',' if (typeof value !== "string") {'," throw new GatewayConfigurationError(`${label} must be a string.`);"," }"," return value.trim();","}","","function asStringArray(value: unknown, label: string): string[] {"," if (value == null) return [];"," if (!Array.isArray(value)) {"," throw new GatewayConfigurationError(`${label} must be an array of non-empty strings.`);"," }"," const items = value.map((item, index) => {",' if (typeof item !== "string" || item.trim() === "") {'," throw new GatewayConfigurationError(`${label}[${index}] must be a non-empty string.`);"," }"," return item.trim();"," });"," const seen = new Set<string>();"," const duplicates: string[] = [];"," for (const item of items) {"," if (seen.has(item)) duplicates.push(item);"," seen.add(item);"," }"," if (duplicates.length > 0) {",' throw new GatewayConfigurationError(`${label} contains duplicates: ${[...new Set(duplicates)].join(", ")}`);'," }"," return items;","}","","function overlap(left: string[], right: string[]): string[] {"," const rightSet = new Set(right);"," return left.filter((item) => rightSet.has(item));","}","","function asPolicyInt(value: unknown, label: string): number {",' if (typeof value !== "number" || !Number.isInteger(value) || value <= 0 || value > MAX_TIMEOUT_MS) {'," throw new GatewayConfigurationError(`${label} must be a positive integer between 1 and ${MAX_TIMEOUT_MS}.`);"," }"," return value;","}","","function asEnvInt(value: string, label: string): number {"," if (!/^[1-9]\\d*$/.test(value.trim())) {"," throw new GatewayConfigurationError(`${label} must be a positive integer between 1 and ${MAX_TIMEOUT_MS}.`);"," }"," return asPolicyInt(Number(value.trim()), label);","}","","function asNonNegativeNumber(value: unknown, label: string): number {",' if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {'," throw new GatewayConfigurationError(`${label} must be a finite non-negative number.`);"," }"," return value;","}","","function asMaxPrice(value: unknown): MaxPrice | null | undefined {"," if (value === undefined || value === null) return value;"," if (!isRecord(value)) {",' throw new GatewayConfigurationError("provider.maxPrice must be an object.");'," }",' rejectUnknownKeys(value, MAX_PRICE_KEYS, "provider.maxPrice");'," const mapped: MaxPrice = {};",' for (const key of ["prompt", "completion", "request", "image", "audio"] as const) {'," const entry = value[key];"," if (entry === undefined) continue;",' if (typeof entry !== "string" || !/^(?:0|[1-9]\\d*)(?:\\.\\d+)?$/.test(entry) || !Number.isFinite(Number(entry)) || Number(entry) < 0) {'," throw new GatewayConfigurationError(`provider.maxPrice.${key} must be a non-negative numeric string.`);"," }"," mapped[key] = entry;"," }"," return mapped;","}","","function asPercentileOrNumber(value: unknown, label: string): number | PercentileCutoffs | null | undefined {"," if (value === undefined || value === null) return value;",' if (typeof value === "number") return asNonNegativeNumber(value, label);'," if (!isRecord(value)) {"," throw new GatewayConfigurationError(`${label} must be a number or percentile object.`);"," }"," rejectUnknownKeys(value, PERCENTILE_KEYS, label);"," const mapped: PercentileCutoffs = {};",' for (const key of ["p50", "p75", "p90", "p99"] as const) {'," const entry = value[key];"," if (entry === undefined) continue;"," mapped[key] = entry === null ? null : asNonNegativeNumber(entry, `${label}.${key}`);"," }"," return mapped;","}","","function asSort(value: unknown): ProviderSort | ProviderSortConfig | null | undefined {"," if (value === undefined || value === null) return value;",' if (typeof value === "string") {'," if (!SORT_VALUES.has(value)) {",' throw new GatewayConfigurationError("provider.sort must be price, throughput, latency, or exacto.");'," }"," return value as ProviderSort;"," }"," if (!isRecord(value)) {",' throw new GatewayConfigurationError("provider.sort must be a string or { by, partition } object.");'," }",' rejectUnknownKeys(value, new Set(["by", "partition"]), "provider.sort");'," const by = value.by;"," const partition = value.partition;",' if (by != null && (typeof by !== "string" || !SORT_VALUES.has(by))) {',' throw new GatewayConfigurationError("provider.sort.by must be price, throughput, latency, or exacto.");'," }",' if (partition != null && (typeof partition !== "string" || !PARTITION_VALUES.has(partition))) {',' throw new GatewayConfigurationError("provider.sort.partition must be model or none.");'," }"," return {"," by: by == null ? by : (by as ProviderSort),"," partition: partition == null ? partition : (partition as SortPartition),"," };","}","","function asQuantizations(value: unknown): Quantization[] | null | undefined {"," if (value === undefined || value === null) return value;",' const items = asStringArray(value, "provider.quantizations");'," for (const item of items) {"," if (!QUANTIZATION_VALUES.has(item)) {"," throw new GatewayConfigurationError(`unsupported provider.quantizations value: ${item}`);"," }"," }"," return items as Quantization[];","}","","export function projectRootFromModule(moduleUrl: string): string {"," return dirname(fileURLToPath(moduleUrl));","}","","export function loadPolicyFile(root: string): unknown {"," const policyPath = join(root, POLICY_FILE_NAME);"," try {",' return JSON.parse(readFileSync(policyPath, "utf8")) as unknown;'," } catch (error) {"," throw new GatewayConfigurationError("," `Unable to read ${POLICY_FILE_NAME}: ${error instanceof Error ? error.message : String(error)}`"," );"," }","}","","function parsePolicy(value: unknown): GatewayPolicyFile {"," if (!isRecord(value)) {",' throw new GatewayConfigurationError("gateway.policy.json must be a JSON object.");'," }",' rejectUnknownKeys(value, ROOT_KEYS, "gateway.policy.json");',' const models = value.models === undefined ? undefined : asStringArray(value.models, "models");'," let provider: TypeScriptProviderPolicy | null | undefined;"," if (value.provider === undefined || value.provider === null) {"," provider = value.provider;"," } else {"," if (!isRecord(value.provider)) {",' throw new GatewayConfigurationError("provider must be an object.");'," }",' rejectUnknownKeys(value.provider, PROVIDER_KEYS, "provider");'," const dataCollection = value.provider.dataCollection;",' if (dataCollection != null && dataCollection !== "allow" && dataCollection !== "deny") {',' throw new GatewayConfigurationError("provider.dataCollection must be allow or deny.");'," }"," provider = {",' allowFallbacks: optionalBoolean(value.provider.allowFallbacks, "provider.allowFallbacks"),',' requireParameters: optionalBoolean(value.provider.requireParameters, "provider.requireParameters"),',' zdr: optionalBoolean(value.provider.zdr, "provider.zdr"),',' enforceDistillableText: optionalBoolean(value.provider.enforceDistillableText, "provider.enforceDistillableText"),'," dataCollection: dataCollection as DataCollection | null | undefined,",' only: value.provider.only == null ? value.provider.only : asStringArray(value.provider.only, "provider.only"),',' ignore: value.provider.ignore == null ? value.provider.ignore : asStringArray(value.provider.ignore, "provider.ignore"),',' order: value.provider.order == null ? value.provider.order : asStringArray(value.provider.order, "provider.order"),'," sort: asSort(value.provider.sort),"," quantizations: asQuantizations(value.provider.quantizations),"," maxPrice: asMaxPrice(value.provider.maxPrice),",' preferredMinThroughput: asPercentileOrNumber(value.provider.preferredMinThroughput, "provider.preferredMinThroughput"),',' preferredMaxLatency: asPercentileOrNumber(value.provider.preferredMaxLatency, "provider.preferredMaxLatency"),'," };"," }"," return {"," models,"," provider,",' timeoutMs: value.timeoutMs === undefined || value.timeoutMs === null ? value.timeoutMs : asPolicyInt(value.timeoutMs, "timeoutMs"),',' httpReferer: optionalString(value.httpReferer, "httpReferer") ?? null,',' appTitle: optionalString(value.appTitle, "appTitle") ?? null,'," };","}","","export function loadGatewayConfig(input: {"," env?: NodeJS.ProcessEnv;"," policy?: unknown;"," root?: string;","} = {}): GatewayConfig {"," const env = input.env ?? process.env;"," const policy = parsePolicy(",' input.policy ?? loadPolicyFile(input.root ?? join(projectRootFromModule(import.meta.url), "..", ".."))'," );",' const apiKey = env.OPENROUTER_API_KEY?.trim() ?? "";',' const model = env.OPENROUTER_MODEL?.trim() ?? "";'," if (!apiKey) {",' throw new GatewayConfigurationError("OPENROUTER_API_KEY is required before any OpenRouter request.");'," }"," if (!model) {",' throw new GatewayConfigurationError("OPENROUTER_MODEL is required. Choose a model slug; this project does not default one.");'," }"," const models = policy.models ?? [];"," if (models.includes(model)) {",' throw new GatewayConfigurationError("models fallbacks must not repeat OPENROUTER_MODEL.");'," }"," const provider = policy.provider ?? undefined;"," const only = provider?.only ?? [];"," const ignore = provider?.ignore ?? [];"," const order = provider?.order ?? [];"," const denied = overlap(only, ignore);"," if (denied.length > 0) {",' throw new GatewayConfigurationError(`provider.only and provider.ignore overlap: ${denied.join(", ")}`);'," }"," const orderedIgnored = overlap(order, ignore);"," if (orderedIgnored.length > 0) {",' throw new GatewayConfigurationError(`provider.order includes ignored providers: ${orderedIgnored.join(", ")}`);'," }",' if (provider?.zdr === true && provider.dataCollection === "allow") {',' throw new GatewayConfigurationError("provider.zdr=true cannot be combined with dataCollection=allow.");'," }"," const timeoutRaw = env.OPENROUTER_TIMEOUT_MS?.trim();"," const timeoutMs = timeoutRaw",' ? asEnvInt(timeoutRaw, "OPENROUTER_TIMEOUT_MS")'," : policy.timeoutMs == null"," ? DEFAULT_TIMEOUT_MS",' : asPolicyInt(policy.timeoutMs, "timeoutMs");'," const httpReferer = env.OPENROUTER_HTTP_REFERER?.trim() || policy.httpReferer || undefined;"," const appTitle = env.OPENROUTER_APP_TITLE?.trim() || policy.appTitle || undefined;"," return {"," apiKey,"," model,"," models,"," timeoutMs,"," httpReferer: httpReferer || undefined,"," appTitle: appTitle || undefined,"," provider,"," };","}","","export function toSdkProvider(provider: TypeScriptProviderPolicy | undefined): ProviderPreferences | undefined {"," if (!provider) return undefined;"," const mapped: ProviderPreferences = {};"," if (provider.allowFallbacks != null) mapped.allowFallbacks = provider.allowFallbacks;"," if (provider.order && provider.order.length > 0) mapped.order = provider.order;"," if (provider.only && provider.only.length > 0) mapped.only = provider.only;"," if (provider.ignore && provider.ignore.length > 0) mapped.ignore = provider.ignore;"," if (provider.sort != null) mapped.sort = provider.sort;"," if (provider.quantizations && provider.quantizations.length > 0) mapped.quantizations = provider.quantizations;"," if (provider.requireParameters != null) mapped.requireParameters = provider.requireParameters;"," if (provider.zdr != null) mapped.zdr = provider.zdr;"," if (provider.dataCollection != null) mapped.dataCollection = provider.dataCollection;"," if (provider.enforceDistillableText != null) mapped.enforceDistillableText = provider.enforceDistillableText;"," if (provider.maxPrice) mapped.maxPrice = provider.maxPrice;"," if (provider.preferredMinThroughput != null) mapped.preferredMinThroughput = provider.preferredMinThroughput;"," if (provider.preferredMaxLatency != null) mapped.preferredMaxLatency = provider.preferredMaxLatency;"," return Object.keys(mapped).length > 0 ? mapped : undefined;","}"])}function ya(){return h(["import { HTTPClient, OpenRouter } from '@openrouter/sdk';","import { loadGatewayConfig, toSdkProvider, type GatewayConfig } from './config.js';","import { GatewayConfigurationError, toGatewayUpstreamError } from './errors.js';","import type { GenerateInput, GenerateResult, ModelGateway } from './port.js';","","export type OpenRouterGatewayOptions = {"," config?: GatewayConfig;"," httpClient?: HTTPClient;","};","","function asText(value: unknown): string {",' if (typeof value === "string") return value;',' if (Array.isArray(value)) return value.map((part) => asText(part)).join("");',' if (value && typeof value === "object" && "text" in value) {'," return asText((value as { text?: unknown }).text);"," }",' return "";',"}","","function isCancellableError(error: unknown): boolean {"," if (!(error instanceof Error)) return false;"," return (",' error.name === "AbortError" ||',' error.name === "TimeoutError" ||',' error.name === "RequestAbortedError" ||',' error.name === "RequestTimeoutError"'," );","}","","// Pinned @openrouter/sdk EventStream extends ReadableStream and has no close().","// Cancellation is async iterator return() -> ReadableStream.cancel(). If the","// stream already completed or errored, cancel() may be a no-op; the gateway","// still drops the reference and remains usable for a later request.","const releasedStreams = new WeakSet<object>();","","async function releaseOpenedStream(stream: unknown, iterator?: AsyncIterator<unknown>): Promise<void> {",' if (!stream || typeof stream !== "object" || releasedStreams.has(stream)) return;'," releasedStreams.add(stream);"," try {",' if (iterator && typeof iterator.return === "function") {'," await iterator.return(undefined);"," return;"," }"," const closer = (stream as { close?: () => unknown }).close;",' if (typeof closer === "function") {'," await closer.call(stream);"," return;"," }"," const cancel = (stream as { cancel?: (reason?: unknown) => unknown }).cancel;",' if (typeof cancel === "function") {'," await cancel.call(stream);"," }"," } catch {"," // Idempotent cleanup must not hide the original error."," }","}","","export class OpenRouterModelGateway implements ModelGateway {"," private readonly config: GatewayConfig;"," private readonly client: OpenRouter;"," private closed = false;",""," constructor(options: OpenRouterGatewayOptions = {}) {"," this.config = options.config ?? loadGatewayConfig();"," this.client = new OpenRouter({"," apiKey: this.config.apiKey,"," httpReferer: this.config.httpReferer,"," appTitle: this.config.appTitle,"," timeoutMs: this.config.timeoutMs,",' retryConfig: { strategy: "none" },'," httpClient: options.httpClient,"," });"," }",""," async generate(input: GenerateInput): Promise<GenerateResult> {"," this.assertOpen();"," this.assertMessages(input.messages);"," try {"," const completion = await this.client.chat.send("," {"," chatRequest: {"," model: this.config.model,"," messages: input.messages,"," models: this.config.models.length > 0 ? this.config.models : undefined,"," provider: toSdkProvider(this.config.provider),"," stream: false,"," },"," },"," { timeoutMs: this.config.timeoutMs, signal: input.signal }"," );"," if (completion instanceof ReadableStream) {",' throw new GatewayConfigurationError("Expected a non-streaming OpenRouter response.");'," }"," const payload = completion as {"," id?: string;"," model?: string;"," choices?: Array<{ message?: { content?: unknown } }>;"," };"," return {"," text: asText(payload.choices?.[0]?.message?.content),"," model: payload.model,"," id: payload.id,"," };"," } catch (error) {"," throw this.normalizeCaught(error);"," }"," }",""," async *stream(input: GenerateInput): AsyncGenerator<string, void, void> {"," this.assertOpen();"," this.assertMessages(input.messages);"," let opened: unknown;"," let iterator: AsyncIterator<unknown> | undefined;"," try {"," opened = await this.client.chat.send("," {"," chatRequest: {"," model: this.config.model,"," messages: input.messages,"," models: this.config.models.length > 0 ? this.config.models : undefined,"," provider: toSdkProvider(this.config.provider),"," stream: true,"," },"," },"," { timeoutMs: this.config.timeoutMs, signal: input.signal }"," );",' if (opened && typeof opened === "object" && Symbol.asyncIterator in opened) {'," iterator = (opened as AsyncIterable<unknown>)[Symbol.asyncIterator]();"," } else if (opened instanceof ReadableStream) {"," const reader = opened.getReader();"," iterator = {"," next: () => reader.read(),"," return: async () => {"," try {"," await reader.cancel();"," } catch {"," // Reader cancel is best-effort."," }"," reader.releaseLock();"," return { done: true, value: undefined };"," },"," };"," } else {"," return;"," }"," for (;;) {"," const step = await iterator.next();"," if (step.done) break;"," const value = step.value as { choices?: Array<{ delta?: { content?: unknown } }> } | string;",' const text = typeof value === "string" ? value : asText(value.choices?.[0]?.delta?.content);'," if (text) yield text;"," }"," } catch (error) {"," throw this.normalizeCaught(error);"," } finally {"," await releaseOpenedStream(opened, iterator);"," }"," }",""," async close(): Promise<void> {"," if (this.closed) return;"," this.closed = true;"," const closer = (this.client as { close?: () => unknown }).close;",' if (typeof closer === "function") {'," await closer.call(this.client);"," }"," }",""," private assertOpen(): void {"," if (this.closed) {",' throw new GatewayConfigurationError("OpenRouterModelGateway is closed.");'," }"," }","",' private assertMessages(messages: GenerateInput["messages"]): void {'," if (!Array.isArray(messages) || messages.length === 0) {",' throw new GatewayConfigurationError("messages must not be empty.");'," }"," }",""," private normalizeCaught(error: unknown): Error {"," if (error instanceof GatewayConfigurationError) return error;"," if (isCancellableError(error)) return error as Error;"," return toGatewayUpstreamError(error);"," }","}"])}function ha(){return h(["import { HTTPClient } from '@openrouter/sdk';","import { loadGatewayConfig } from './config.js';","import { OpenRouterModelGateway } from './openrouter-gateway.js';","import { GatewayConfigurationError, mapUpstreamError } from './errors.js';","","function promptFromArgv(): string {",' const args = process.argv.slice(2).filter((arg) => arg !== "--smoke");',' return args.join(" ").trim() || "Reply with a short acknowledgement.";',"}","",'function chatCompletionPayload(content: string, model = "openrouter/test-model"): string {'," return JSON.stringify({",' id: "smoke-non-stream",'," created: 1,"," model,",' object: "chat.completion",'," system_fingerprint: null,",' choices: [{ finish_reason: "stop", index: 0, message: { role: "assistant", content } }],'," });","}","","function chatStreamSse(parts: string[]): string {"," return ("," parts"," .map((content, index) =>"," `data: ${JSON.stringify({",' id: "smoke-stream",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content }, finish_reason: index === parts.length - 1 ? "stop" : null, index: 0 }],'," })}\\n\\n`"," )",' .join("") + "data: [DONE]\\n\\n"'," );","}","","function smokeClient(): HTTPClient {"," return new HTTPClient({"," fetcher: async (input, init) => {"," const request = input instanceof Request ? input : new Request(input, init);"," const body = await request.text();"," const parsed = JSON.parse(body) as { stream?: boolean; model?: string };"," if (parsed.stream) {",' return new Response(chatStreamSse(["smoke"]), {'," status: 200,",' headers: { "content-type": "text/event-stream", "x-request-id": "smoke-stream" },'," });"," }",' return new Response(chatCompletionPayload("smoke-ok", parsed.model), {'," status: 200,",' headers: { "content-type": "application/json", "x-request-id": "smoke-id" },'," });"," },"," });","}","","async function runSmoke(): Promise<void> {",' process.env.OPENROUTER_API_KEY ??= "test-openrouter-key";',' process.env.OPENROUTER_MODEL ??= "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig(),"," httpClient: smokeClient(),"," });"," const generated = await gateway.generate({",' messages: [{ role: "user", content: "ping" }],'," });",' let streamed = "";',' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "ping" }] })) {'," streamed += chunk;"," }"," await gateway.close();"," process.stdout.write(`${generated.text}:${streamed}\\n`);","}","","async function main(): Promise<void> {",' if (process.argv.includes("--smoke")) {'," await runSmoke();"," return;"," }"," const gateway = new OpenRouterModelGateway();"," try {"," const controller = new AbortController();"," const timeout = AbortSignal.timeout(loadGatewayConfig().timeoutMs);",' timeout.addEventListener("abort", () => controller.abort());'," let wrote = false;"," for await (const chunk of gateway.stream({",' messages: [{ role: "user", content: promptFromArgv() }],'," signal: controller.signal,"," })) {"," process.stdout.write(chunk);"," wrote = true;"," }"," if (!wrote) {"," const result = await gateway.generate({",' messages: [{ role: "user", content: promptFromArgv() }],'," signal: controller.signal,"," });"," process.stdout.write(result.text);"," }",' process.stdout.write("\\n");'," } finally {"," await gateway.close();"," }","}","","main().catch((error: unknown) => {"," if (error instanceof GatewayConfigurationError) {"," process.stderr.write(`${error.message}\\n`);"," } else {"," const mapped = mapUpstreamError(error);"," process.stderr.write(`${mapped.message}\\n`);"," }"," process.exitCode = 1;","});"])}function wa(){return h(["export { loadGatewayConfig, toSdkProvider } from './config.js';","export { OpenRouterModelGateway } from './openrouter-gateway.js';","export { redactSecretShapedValues } from './redact.js';","export { mapUpstreamError, GatewayConfigurationError, GatewayUpstreamError } from './errors.js';","export type { ModelGateway } from './port.js';"])}function ka(){return h(["import assert from 'node:assert/strict';","import { mkdtempSync, writeFileSync } from 'node:fs';","import { readFile } from 'node:fs/promises';","import { tmpdir } from 'node:os';","import { dirname, join } from 'node:path';","import { test } from 'node:test';","import { fileURLToPath } from 'node:url';","","import { HTTPClient } from '@openrouter/sdk';","","import { GatewayConfigurationError, loadGatewayConfig, loadPolicyFile, toSdkProvider } from '../src/config.js';","import { GatewayUpstreamError, mapUpstreamError } from '../src/errors.js';","import { OpenRouterModelGateway } from '../src/openrouter-gateway.js';","import { redactSecretShapedValues } from '../src/redact.js';","",'const root = join(dirname(fileURLToPath(import.meta.url)), "..", "..");',`const PARITY_CASES = ${JSON.stringify(it)} as const;`,"","function fakeClient(handler: (request: Request) => Promise<Response> | Response): HTTPClient {"," return new HTTPClient({"," fetcher: async (input, init) => {"," const request = input instanceof Request ? input : new Request(input, init);"," return handler(request);"," },"," });","}","",'function chatCompletionPayload(content: string, model = "openrouter/test-model"): string {'," return JSON.stringify({",' id: "gen-1",'," created: 1,"," model,",' object: "chat.completion",'," system_fingerprint: null,",' choices: [{ finish_reason: "stop", index: 0, message: { role: "assistant", content } }],'," });","}","","function chatStreamSse(parts: string[]): string {"," return ("," parts"," .map((content, index) =>"," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content }, finish_reason: index === parts.length - 1 ? "stop" : null, index: 0 }],'," })}\\n\\n`"," )",' .join("") + "data: [DONE]\\n\\n"'," );","}","",'test("redacts keys, authorization, and url secrets", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-live-secret";'," const raw =",' "Authorization: Bearer sk-or-live-secret called https://example.test/v1?api_key=sk-or-live-secret";'," const redacted = redactSecretShapedValues(raw);",' assert.equal(redacted.includes("sk-or-live-secret"), false);'," assert.match(redacted, /\\[redacted\\]/);","});","",'test("configuration fails before a network request", async () => {'," const previousKey = process.env.OPENROUTER_API_KEY;"," const previousModel = process.env.OPENROUTER_MODEL;"," delete process.env.OPENROUTER_API_KEY;",' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," try {"," assert.throws(() => loadGatewayConfig({ root }), GatewayConfigurationError);"," } finally {"," if (previousKey === undefined) delete process.env.OPENROUTER_API_KEY;"," else process.env.OPENROUTER_API_KEY = previousKey;"," if (previousModel === undefined) delete process.env.OPENROUTER_MODEL;"," else process.env.OPENROUTER_MODEL = previousModel;"," }","});","",'test("rejects overlapping provider allow and deny lists", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," assert.throws("," () =>"," loadGatewayConfig({",' policy: { provider: { only: ["openai"], ignore: ["openai"] } },'," }),"," /overlap/"," );","});","",'test("maps TypeScript SDK provider field names", () => {'," const mapped = toSdkProvider({"," allowFallbacks: false,"," zdr: true,",' dataCollection: "deny",',' sort: "latency",',' order: ["anthropic"],'," });"," assert.deepEqual(mapped, {"," allowFallbacks: false,"," zdr: true,",' dataCollection: "deny",',' sort: "latency",',' order: ["anthropic"],'," });","});","",'test("non-stream generation uses injected transport and records policy", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";',' let body = "";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({",' policy: { provider: { zdr: true, dataCollection: "deny" }, models: ["openrouter/fallback"] },'," }),"," httpClient: fakeClient(async (request) => {"," body = await request.text();",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }),"," });",' const result = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(result.text, "ok");'," const parsed = JSON.parse(body) as {"," model: string;"," models?: string[];"," provider?: { zdr?: boolean; data_collection?: string; dataCollection?: string };"," };",' assert.equal(parsed.model, "openrouter/test-model");',' assert.deepEqual(parsed.models, ["openrouter/fallback"]);'," assert.equal(parsed.provider?.zdr, true);",' assert.equal(parsed.provider?.data_collection ?? parsed.provider?.dataCollection, "deny");'," await gateway.close();","});","",'test("streaming consumes chunks and cancels the reader", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async () => {",' return new Response(chatStreamSse(["hel", "lo"]), {'," status: 200,",' headers: { "content-type": "text/event-stream" },'," });"," }),"," });",' let text = "";',' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," text += chunk;"," }",' assert.equal(text.includes("hel") || text.includes("hello"), true);'," await gateway.close();","});","",'test("maps upstream status and request id without leaking secrets", () => {'," const headers = new Headers({",' authorization: "Bearer sk-or-secret",',' "x-request-id": "req-123",',' "retry-after": "2",'," });"," const mapped = mapUpstreamError({",' message: "Authorization: Bearer sk-or-secret failed",'," statusCode: 429,"," headers,"," });"," assert.equal(mapped.statusCode, 429);",' assert.equal(mapped.requestId, "req-123");',' assert.equal(mapped.retryAfter, "2");',' assert.equal(mapped.message.includes("sk-or-secret"), false);',' assert.equal(mapped.safeHeaders?.authorization ?? mapped.safeHeaders?.Authorization, "[redacted]");',"});","",'test("timeout uses AbortSignal", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: { ...loadGatewayConfig({ policy: {} }), timeoutMs: 20 },"," httpClient: fakeClient(async (request) => {"," const signal = request.signal;"," await new Promise((_, reject) => {",' const fail = () => reject(signal.reason ?? new Error("aborted"));'," const watchdog = setTimeout(() => {",' reject(new Error("AbortSignal was not forwarded to transport"));'," }, 1000);"," const abort = () => {"," clearTimeout(watchdog);"," fail();"," };"," if (signal.aborted) {"," abort();"," return;"," }",' signal.addEventListener("abort", abort, { once: true });'," });",' return new Response("{}", { status: 200 });'," }),"," });"," await assert.rejects(",' () => gateway.generate({ messages: [{ role: "user", content: "hi" }] }),'," (error: unknown) => {"," const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);"," return /timed out|aborted by client|AbortError|TimeoutError|This operation was aborted/i.test(text);"," }"," );"," const cancelled = new AbortController();"," cancelled.abort();"," await assert.rejects(() =>"," gateway.generate({",' messages: [{ role: "user", content: "hi" }],'," signal: cancelled.signal,"," })"," );"," await gateway.close();","});","",'test("timeoutMs is loaded from OPENROUTER_TIMEOUT_MS", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";',' process.env.OPENROUTER_TIMEOUT_MS = "1500";'," const config = loadGatewayConfig({ policy: {} });"," assert.equal(config.timeoutMs, 1500);"," delete process.env.OPENROUTER_TIMEOUT_MS;","});","",'test("policy file stays source-controlled and secret-free", async () => {',' const policy = await readFile(join(root, "gateway.policy.json"), "utf8");',' assert.equal(policy.includes("sk-"), false);',' assert.equal(policy.includes("OPENROUTER_API_KEY"), false);',"});","",'test("rejects every invalid policy category before transport", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const cases: Array<[string, unknown, RegExp]> = [",' ["root array", [], /JSON object/],',' ["unknown root field", { widget: true }, /unknown field/],',' ["models not array", { models: "openai/gpt" }, /models must be an array/],',' ["empty model", { models: [" "] }, /non-empty string/],',' ["duplicate models", { models: ["a", "a"] }, /duplicates/],',' ["primary in fallbacks", { models: ["openrouter/test-model"] }, /repeat OPENROUTER_MODEL/],',' ["provider string", { provider: "openai" }, /provider must be an object/],',' ["unknown provider field", { provider: { fallbacks: true } }, /unknown field/],',' ["allowFallbacks string", { provider: { allowFallbacks: "yes" } }, /boolean/],',' ["requireParameters number", { provider: { requireParameters: 1 } }, /boolean/],',' ["zdr string", { provider: { zdr: "true" } }, /boolean/],',' ["only ignore overlap", { provider: { only: ["openai"], ignore: ["openai"] } }, /overlap/],',' ["order includes ignore", { provider: { order: ["openai"], ignore: ["openai"] } }, /ignored providers/],',' ["dataCollection invalid", { provider: { dataCollection: "never" } }, /allow or deny/],',' ["sort invalid", { provider: { sort: "cheap" } }, /sort/],',' ["sort partition invalid", { provider: { sort: { by: "price", partition: "all" } } }, /partition/],',' ["quantization invalid", { provider: { quantizations: ["int5"] } }, /quantizations/],',' ["maxPrice number", { provider: { maxPrice: { prompt: 1 } } }, /numeric string/],',' ["maxPrice unknown key", { provider: { maxPrice: { tokens: "1" } } }, /unknown field/],',' ["maxPrice negative", { provider: { maxPrice: { prompt: "-1" } } }, /numeric string/],',' ["maxPrice malformed", { provider: { maxPrice: { prompt: "not-a-price" } } }, /numeric string/],',' ["maxPrice overflow", { provider: { maxPrice: { prompt: "9".repeat(400) } } }, /numeric string/],',' ["percentile unknown", { provider: { preferredMaxLatency: { p95: 1 } } }, /unknown field/],',' ["percentile negative", { provider: { preferredMinThroughput: { p50: -1 } } }, /non-negative/],',' ["timeout float", { timeoutMs: 30.5 }, /positive integer/],',' ["timeout zero", { timeoutMs: 0 }, /positive integer/],',' ["timeout too large", { timeoutMs: 600001 }, /positive integer/],',' ["timeout string", { timeoutMs: "30000" }, /positive integer/],',' ["attribution number", { httpReferer: 1 }, /string/],',' ["zdr with allow", { provider: { zdr: true, dataCollection: "allow" } }, /zdr/],'," ];"," for (const [name, policy, pattern] of cases) {"," assert.throws("," () => loadGatewayConfig({ policy }),"," (error: unknown) => {"," assert.equal(error instanceof GatewayConfigurationError, true, name);"," assert.match((error as Error).message, pattern, name);"," return true;"," }"," );"," }","});","",'test("accepts official provider policy boundary values", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const config = loadGatewayConfig({"," policy: {",' models: ["openrouter/fallback"],'," timeoutMs: 1,",' httpReferer: "https://example.test",',' appTitle: "gateway",'," provider: {"," allowFallbacks: false,"," requireParameters: true,"," zdr: true,",' dataCollection: "deny",'," enforceDistillableText: true,",' only: ["anthropic"],',' ignore: ["openai"],',' order: ["anthropic"],',' sort: { by: "exacto", partition: "none" },',' quantizations: ["fp16"],',' maxPrice: { prompt: "0", completion: "1.5", request: "2", image: "3", audio: "4" },'," preferredMinThroughput: { p50: 0, p75: 1, p90: 2, p99: 3 },"," preferredMaxLatency: 1.5,"," },"," },"," });"," assert.equal(config.timeoutMs, 1);"," const mapped = toSdkProvider(config.provider);"," assert.equal(mapped?.enforceDistillableText, true);",' assert.deepEqual(mapped?.sort, { by: "exacto", partition: "none" });',' assert.equal(mapped?.maxPrice?.prompt, "0");'," const upper = loadGatewayConfig({ policy: { timeoutMs: 600000 } });"," assert.equal(upper.timeoutMs, 600000);","});","",'test("policy parity corpus executes the generated validator", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," for (const entry of PARITY_CASES) {",' if (entry.decision === "reject") {'," assert.throws("," () => loadGatewayConfig({ policy: entry.typescript }),"," (error: unknown) => {"," assert.equal(error instanceof GatewayConfigurationError, true, entry.id);"," if (entry.needle) assert.match((error as Error).message, new RegExp(entry.needle), entry.id);"," return true;"," }"," );"," } else {"," const config = loadGatewayConfig({ policy: entry.typescript });"," const mapped = toSdkProvider(config.provider);",' if (entry.id === "max-price-zero") assert.equal(mapped?.maxPrice?.prompt, "0");',' if (entry.id === "max-price-micro") assert.equal(mapped?.maxPrice?.prompt, "0.000001");',' if (entry.id === "max-price-positive") assert.equal(mapped?.maxPrice?.completion, "1.5");'," }"," }","});","",'test("finite maxPrice strings are preserved or rejected", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const accepted = loadGatewayConfig({",' policy: { provider: { maxPrice: { prompt: "0", completion: "0.000001", request: "12.5" } } },'," });"," const mapped = toSdkProvider(accepted.provider)?.maxPrice;",' assert.equal(mapped?.prompt, "0");',' assert.equal(mapped?.completion, "0.000001");',' assert.equal(mapped?.request, "12.5");',' for (const value of ["-1", "not-a-price", "9".repeat(400), "1e6", "+1", "inf", "nan"]) {'," assert.throws("," () => loadGatewayConfig({ policy: { provider: { maxPrice: { prompt: value } } } }),"," GatewayConfigurationError"," );"," }"," assert.throws(",' () => loadGatewayConfig({ policy: { provider: { maxPrice: { tokens: "1" } } } }),'," GatewayConfigurationError"," );","});","",'test("rejects non-finite numeric policy values", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," assert.throws("," () => loadGatewayConfig({ policy: { provider: { preferredMaxLatency: Number.POSITIVE_INFINITY } } }),"," GatewayConfigurationError"," );"," assert.throws("," () => loadGatewayConfig({ policy: { provider: { preferredMinThroughput: Number.NaN } } }),"," GatewayConfigurationError"," );"," assert.throws("," () => loadGatewayConfig({ policy: { provider: { preferredMaxLatency: { p50: Number.NEGATIVE_INFINITY } } } }),"," GatewayConfigurationError"," );","});","",'test("missing policy file is a configuration error", () => {',' const dir = mkdtempSync(join(tmpdir(), "or-missing-policy-"));'," assert.throws(() => loadPolicyFile(dir), GatewayConfigurationError);","});","",'test("malformed policy JSON is a configuration error", () => {',' const dir = mkdtempSync(join(tmpdir(), "or-policy-"));',' writeFileSync(join(dir, "gateway.policy.json"), "{");'," assert.throws(() => loadPolicyFile(dir), GatewayConfigurationError);","});","",'test("empty messages fail before transport", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," let called = false;"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async () => {"," called = true;",' return new Response(chatCompletionPayload("nope"), { status: 200, headers: { "content-type": "application/json" } });'," }),"," });"," await assert.rejects(() => gateway.generate({ messages: [] }), GatewayConfigurationError);"," await assert.rejects(() => gateway.stream({ messages: [] }).next(), GatewayConfigurationError);"," assert.equal(called, false);"," await gateway.close();","});","",'test("empty successful completion returns an empty string", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),",' httpClient: fakeClient(async () => new Response(chatCompletionPayload(""), {'," status: 200,",' headers: { "content-type": "application/json" },'," })),"," });",' const result = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(result.text, "");'," await gateway.close();","});","",'test("close is idempotent and then rejects new work", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),",' httpClient: fakeClient(async () => new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," })),"," });"," await gateway.close();"," await gateway.close();",' await assert.rejects(() => gateway.generate({ messages: [{ role: "user", content: "hi" }] }), GatewayConfigurationError);',"});","",'test("stream-open failure is a redacted upstream error with metadata", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async () =>",' new Response(JSON.stringify({ error: { message: "Authorization: Bearer sk-or-secret" } }), {'," status: 401,"," headers: {",' "content-type": "application/json",',' "x-request-id": "req-open",',' "retry-after": "7",',' authorization: "Bearer sk-or-secret",'," },"," })"," ),"," });"," await assert.rejects(",' () => gateway.stream({ messages: [{ role: "user", content: "hi" }] }).next(),'," (error: unknown) => {"," assert.equal(error instanceof GatewayUpstreamError, true);"," const upstream = error as GatewayUpstreamError;"," assert.equal(upstream.statusCode, 401);",' assert.equal(upstream.requestId, "req-open");',' assert.equal(upstream.retryAfter, "7");',' assert.equal(upstream.message.includes("sk-or-secret"), false);'," return true;"," }"," );"," await gateway.close();","});","",'test("mid-stream failure is normalized, redacted, and cleaned up", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: null, index: 0 }],'," })}\\n\\n`"," )"," );",' controller.error(new Error("Authorization: Bearer sk-or-secret mid-stream"));'," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });"," await assert.rejects("," async () => {",' for await (const _chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," void _chunk;"," }"," },"," (error: unknown) => {"," const text = error instanceof Error ? error.message : String(error);",' assert.equal(text.includes("sk-or-secret"), false);'," assert.equal(error instanceof GatewayConfigurationError, false);"," return /mid-stream|redacted|Unexpected|stream/i.test(text) || error instanceof GatewayUpstreamError;"," }"," );",' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("consumer early stream termination closes the upstream body", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," let cancelled = false;"," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: null, index: 0 }],'," })}\\n\\n`"," )"," );"," },"," cancel() {"," cancelled = true;"," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });",' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {',' assert.equal(chunk.includes("hel"), true);'," break;"," }"," assert.equal(cancelled, true);",' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("normal stream completion stays successful and leaves the gateway reusable", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," let cancelCount = 0;"," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: "stop", index: 0 }],'," })}\\n\\n`"," )"," );",' controller.enqueue(encoder.encode("data: [DONE]\\n\\n"));'," controller.close();"," },"," cancel() {"," cancelCount += 1;"," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });"," const chunks: string[] = [];",' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," chunks.push(chunk);"," }",' assert.equal(chunks.join("").includes("hel"), true);'," // Pinned EventStream closes a completed ReadableStream without calling cancel()."," // Iterator.return() is still invoked once; it is a no-op on an already-closed stream."," assert.ok(cancelCount === 0 || cancelCount === 1);",' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("cleanup failure does not mask upstream or convert success into an error", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: "stop", index: 0 }],'," })}\\n\\n`"," )"," );",' controller.enqueue(encoder.encode("data: [DONE]\\n\\n"));'," controller.close();"," },"," cancel() {",' throw new Error("cleanup-failed");'," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });"," const chunks: string[] = [];",' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," chunks.push(chunk);"," }",' assert.equal(chunks.join("").includes("hel"), true);',' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("already-aborted caller signal is rejected without hanging", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," if (request.signal.aborted) {",' throw request.signal.reason ?? new Error("aborted");'," }"," const watchdog = setTimeout(() => undefined, 1000);"," await new Promise((_, reject) => {"," const fail = () => {"," clearTimeout(watchdog);",' reject(request.signal.reason ?? new Error("aborted"));'," };",' request.signal.addEventListener("abort", fail, { once: true });',' setTimeout(() => reject(new Error("AbortSignal was not forwarded to transport")), 1000);'," });",' return new Response("{}", { status: 200 });'," }),"," });"," const cancelled = new AbortController();"," cancelled.abort();"," await assert.rejects(() =>"," gateway.stream({",' messages: [{ role: "user", content: "hi" }],'," signal: cancelled.signal,"," }).next()"," );"," await gateway.close();","});"])}async function Ea(e){let t=zr(e.projectName);await e$1(x.join(e.projectPath,"package.json"),la(t)),await e$1(x.join(e.projectPath,"tsconfig.json"),ca()),await e$1(x.join(e.projectPath,de),da()),await e$1(x.join(e.projectPath,".env.example"),pa()),await e$1(x.join(e.projectPath,".gitignore"),st),await e$1(x.join(e.projectPath,"README.md"),ma(e.projectName)),await e$1(x.join(e.projectPath,"src","redact.ts"),ua()),await e$1(x.join(e.projectPath,"src","errors.ts"),fa()),await e$1(x.join(e.projectPath,"src","port.ts"),ga()),await e$1(x.join(e.projectPath,"src","config.ts"),_a()),await e$1(x.join(e.projectPath,"src","openrouter-gateway.ts"),ya()),await e$1(x.join(e.projectPath,"src","main.ts"),ha()),await e$1(x.join(e.projectPath,"src","index.ts"),wa()),await e$1(x.join(e.projectPath,"tests","gateway.test.ts"),ka());}var no={id:"openrouter-typescript",gatewayId:"openrouter",gatewayName:"OpenRouter",kitId:"gateway.openrouter.typescript",aliases:["gateway.openrouter.typescript","openrouter.typescript","gateway.openrouter.ts","openrouter-typescript"],label:"OpenRouter \xB7 TypeScript",runtime:"node",requiredEnvironment:["OPENROUTER_API_KEY","OPENROUTER_MODEL"],versionBaselineId:"openrouter-typescript",capabilities:pe,lifecycle:{stages:lt},attach:"unsupported",generate:Ea};function ao(e){return Object.freeze({id:e.id,gatewayId:e.gatewayId,gatewayName:e.gatewayName,kitId:e.kitId,aliases:Object.freeze([...e.aliases]),label:e.label,runtime:e.runtime,requiredEnvironment:Object.freeze([...e.requiredEnvironment]),versionBaselineId:e.versionBaselineId,capabilities:pe,lifecycle:Object.freeze({stages:Object.freeze([...e.lifecycle.stages])}),attach:"unsupported"})}function so(e){return {id:e.kitId,aliases:[...e.aliases],label:e.label,runtime:e.runtime,adapterId:e.id,gatewayId:e.gatewayId,gatewayName:e.gatewayName,requiredEnvironment:[...e.requiredEnvironment]}}function io(e){if(e.length===0)throw new Error("Model gateway registry requires at least one adapter.");let t=new Set,r=new Set,o=new Map;for(let n of e){if(!n.id||!n.kitId||!n.generate)throw new Error("Model gateway adapter is missing id, kitId, or generate.");if(t.has(n.id))throw new Error(`Duplicate model gateway adapter: ${n.id}`);if(r.has(n.kitId))throw new Error(`Duplicate model gateway kit: ${n.kitId}`);t.add(n.id),r.add(n.kitId);let l=[n.kitId,...n.aliases];for(let c of l){let i=c.trim().toLowerCase();if(!i)throw new Error(`Empty model gateway alias on ${n.id}.`);let p=o.get(i);if(p&&p!==n.id)throw new Error(`Duplicate model gateway alias: ${c}`);o.set(i,n.id);}}let a=Object.freeze([...e]);return {listAdapters(){return a.map(n=>ao(n))},listKits(){return a.map(n=>structuredClone(so(n)))},lookupKit:n=>{if(!n)return null;let l=n.trim().toLowerCase(),c=a.find(i=>i.kitId.toLowerCase()===l||i.aliases.some(p=>p.toLowerCase()===l));return c?structuredClone(so(c)):null},resolveAdapter(n){let l=a.find(c=>c.kitId===n.id);if(!l)throw new Error(`Unsupported model gateway kit: ${n.id}`);return l},async generate(n){let l=a.find(c=>c.kitId===n.kit.id);if(!l)throw new Error(`Unsupported model gateway kit: ${n.kit.id}`);await l.generate({projectPath:n.projectPath,projectName:n.projectName});}}}var Lt=io([no,ro]);function va(){return Lt.listKits()}function Hc(){return va()}function Aa(e){return Lt.lookupKit(e)}function Jc(e){return Aa(e)!==null}async function Zc(e){await Lt.generate({projectPath:e.projectPath,projectName:e.projectName,kit:e.kit}),await Qr(e);}var Ra=["minimal","python-only","node-only","go-only","java-only","dotnet-only","polyglot","enterprise"],ba=new Set(["minimal","node-only","go-only","java-only","dotnet-only"]),ld=new Set(["python-only","polyglot","enterprise"]);function Ta(e){return typeof e=="string"&&Ra.includes(e)}function cd(e){return Ta(e)&&ba.has(e)}var po={"python-only":"python","node-only":"node","go-only":"go","java-only":"java","dotnet-only":"dotnet"},Oa={python:"Python",node:"Node",go:"Go",java:"Java",dotnet:".NET",php:"PHP",ruby:"Ruby",rust:"Rust",elixir:"Elixir",clojure:"Clojure",scala:"Scala",kotlin:"Kotlin",deno:"Deno",bun:"Bun",c:"C",cpp:"C++",unknown:"Unknown"},Pa={python:"python-only",node:"node-only",go:"go-only",java:"java-only",dotnet:"dotnet-only"},lo=["node","deno","bun","python","go","java","kotlin","scala","dotnet","php","ruby","rust","elixir","clojure","c","cpp","unknown"];function qt(e){return e==="bun"||e==="deno"?"node":e==="kotlin"||e==="scala"?"java":e}function Wt(e){return typeof e!="string"||e.trim().length===0?"minimal":e.trim().toLowerCase()}function xe(e){if(typeof e!="string")return "unknown";let t=e.trim().toLowerCase();return t==="python"||t==="node"||t==="go"||t==="java"||t==="dotnet"||t==="php"||t==="ruby"||t==="rust"||t==="elixir"||t==="clojure"||t==="scala"||t==="kotlin"||t==="deno"||t==="bun"||t==="c"||t==="cpp"?t:t==="c++"||t==="cplusplus"||t==="cc"||t==="clang++"||t==="g++"?"cpp":t==="clang"||t==="gcc"?"c":"unknown"}function pt(e){let t=new Set;for(let r of e)t.add(xe(r));return [...t].sort((r,o)=>lo.indexOf(r)-lo.indexOf(o))}function Sa(e){let t=pt(e).filter(r=>r!=="unknown");if(t.length!==0)return t.length===1?Pa[t[0]]:"polyglot"}function co(e,t={}){return `npx workspai bootstrap --profile ${e}${t.ci?" --ci --json":""}`}function Na(e,t){return e?"none":t==="strict"?"error":"warning"}function Re(e){let t=e.ok?void 0:Sa(e.runtimes)??"polyglot";return {ok:e.ok,checkId:e.checkId,status:e.ok?"passed":"failed",profile:e.profile,runtimes:e.runtimes,message:e.message,recommendedProfile:t,recommendedCommand:t?co(t):void 0,ciRecommendedCommand:t?co(t,{ci:true}):void 0,severity:Na(e.ok,e.mode)}}function dd(e){let t=Wt(e.profile),r=pt(e.runtimes),o=e.mode??"warn",a=po[t];if(a){let s=r.length===0||r.every(n=>qt(n)===a);return Re({ok:s,checkId:`profile.${t}`,profile:t,runtimes:r,message:s?`${t} profile validated for discovered projects.`:`${t} profile mismatch: detected runtimes [${r.join(", ")}].`,mode:o})}if(t==="minimal"){let s=r.filter(c=>c!=="unknown"),l=new Set(s.map(c=>qt(c))).size<=1;return Re({ok:l,checkId:"profile.minimal",profile:t,runtimes:r,message:l?"minimal profile is compatible with detected runtime mix.":`minimal profile mismatch: multiple runtimes detected [${s.join(", ")}].`,mode:o})}return Re({ok:true,checkId:`profile.${t}`,profile:t,runtimes:r,message:`${t} profile accepts the detected runtime mix.`,mode:o})}function pd(e){let t=Wt(e.profile),r=xe(e.runtime),o=e.mode??"warn",a=po[t];if(!a||r==="unknown")return Re({ok:true,checkId:`profile.${t}`,profile:t,runtimes:[r],message:r==="unknown"?`Project "${e.subjectLabel}" runtime is unknown; profile compatibility will be checked by workspace doctor.`:`${t} profile accepts project "${e.subjectLabel}".`,mode:o});let s=qt(r)===a;return Re({ok:s,checkId:`profile.${t}`,profile:t,runtimes:s?[r]:pt([a,r]),message:s?`Project "${e.subjectLabel}" is compatible with workspace profile "${t}".`:`Project "${e.subjectLabel}" is ${Oa[r]}, but workspace profile is "${t}".`,mode:o})}async function md(e){let t$1=(await Promise.all(t(e,"policies.yml").map(async r=>await M.pathExists(r)?r:null))).find(r=>typeof r=="string");if(!t$1)return "warn";try{return (await M.readFile(t$1,"utf-8")).match(/^\s*mode:\s*(warn|strict)\s*(?:#.*)?$/m)?.[1]==="strict"?"strict":"warn"}catch{return "warn"}}async function ud(e){let t$1=(await Promise.all(t(e,"workspace.json").map(async r=>await M.pathExists(r)?r:null))).find(r=>typeof r=="string");if(!t$1)return "minimal";try{let r=await M.readJson(t$1);return Wt(r.profile)}catch{return "minimal"}}async function fd(e,t={}){let r=new Set,o=[],a=new Set,s=[e];for(;s.length>0;){let n=s.pop();if(!n||a.has(n))continue;a.add(n);let l=[];try{l=await xa.promises.readdir(n,{withFileTypes:true});}catch{continue}for(let c of l){if(!c.isDirectory()||c.name.startsWith(".")||["node_modules","dist","build","target","coverage","htmlcov"].includes(c.name))continue;let i=x.join(n,c.name);if(i!==e&&k$1(i))continue;if((await Promise.all([...u(i,"context.json"),...u(i,"project.json")].map(async _=>M.pathExists(_)))).some(Boolean)){o.push(i);continue}s.push(i);}}for(let n of o){if(t.excludeProjectPath&&x.resolve(n)===x.resolve(t.excludeProjectPath))continue;let l=j(n);r.add(xe(m(n,l).runtime));}for(let n of await a$1(e))t.excludeProjectPath&&x.resolve(n.path)===x.resolve(t.excludeProjectPath)||r.add(xe(n.runtime));for(let n of t.additionalRuntimes??[])r.add(xe(n));return pt(r)}function gd(e,t={}){let r=t.ci?e.ciRecommendedCommand:e.recommendedCommand;return r?`Recommended: ${r}`:null}
4737
- export{vs as A,As as B,xs as C,b as D,jt as E,Vr as F,qn as G,Xl as H,Hl as I,Br as J,Jl as K,Zl as L,Vn as M,rt as N,va as O,Hc as P,Aa as Q,Jc as R,Zc as S,ld as T,cd as U,dd as V,pd as W,md as X,ud as Y,fd as Z,gd as _,N as a,Yt as b,Xt as c,Ht as d,Jt as e,Zt as f,Qt as g,er as h,tr as i,Pe as j,Za as k,fe as l,Qa as m,es as n,wo as o,ts as p,fs as q,yt as r,gs as s,Oo as t,_s as u,ws as v,U as w,Do as x,ks as y,Es as z};
4745
+ `}function pa(){return ct({attributionComment:["# Optional application attribution. These map to the official TypeScript SDK","# fields httpReferer and appTitle."],refererName:"OPENROUTER_HTTP_REFERER",titleName:"OPENROUTER_APP_TITLE"})}function ma(e){return h([`# ${e}`,"","This is a server-owned OpenRouter Model Gateway generated by Workspai.","It is an AI Gateway, not an agent runtime. OpenRouter owns provider and","model routing. Workspai owns generation, lifecycle, and verification.","","The official Client SDK is `@openrouter/sdk` `"+oo+"`.","A model identifier is runtime configuration, not a project kit.","","## Security","","- Keep `OPENROUTER_API_KEY` on the server. Do not put it in browser bundles,"," `VITE_`, `NEXT_PUBLIC_`, or other client-side environment prefixes.","- Diagnostics redact Authorization headers, API keys, URL secrets, and request bodies.","- Configuration is validated before any network request.","- This project does not disable TLS verification and does not enable SDK debug logging.","- Privacy and routing policy live in `gateway.policy.json` so they stay auditable.","- When a privacy or routing field is omitted, OpenRouter upstream defaults remain in"," effect. The SDK documents `dataCollection` default as `allow` when unset.","","## Configure","","1. Copy `.env.example` into your secret store or shell. Do not commit `.env`.","2. Set `OPENROUTER_API_KEY` and `OPENROUTER_MODEL`.","3. Edit `gateway.policy.json` for official TypeScript SDK provider controls:"," fallbacks, order/allow/deny, sort (`price`/`throughput`/`latency`/`exacto`,"," optional `{ by, partition }` where `partition` is `model` or `none`),"," quantizations, requireParameters, ZDR, dataCollection, enforceDistillableText,"," maxPrice, and preferred throughput or latency. Unknown fields are rejected.","",'Contradictory policy fails before a request is sent. ZDR and `dataCollection: "deny"`',"are never silently dropped. Application-side retries are disabled so they cannot","duplicate billable requests; OpenRouter still applies its own provider fallbacks","when `allowFallbacks` is not false. An empty successful completion returns an empty","string. Timeout must be a bounded positive integer (1 to 600000 ms). `maxPrice`","values must be numeric strings that stay finite after `Number(...)`; overflow-to-","infinity strings are rejected. Streaming cleanup prefers async iterator `return()`,","then SDK `close()`, then `cancel()`, and never invokes two of those on the same","stream. Pinned `@openrouter/sdk` `EventStream` extends `ReadableStream` and has no","`close()`; if a stream already errored, `cancel()` may not run.","","Capabilities such as tools, JSON schema, streaming, images, audio, and embeddings","vary by model and provider. This starter only claims chat generation and streaming.","","## Commands","","Run these from the project root after installing dependencies locally:","","```bash","npm install","npm test","npm run typecheck","npm run build","npm run smoke","npm start","```","","`npm test` and `npm run smoke` inject a fake transport. They do not call OpenRouter","and do not require `OPENROUTER_API_KEY`. `npm start` is the live entrypoint and","requires credentials.","","Workspace Run uses the same scripts: `init` -> `npm install`, `test` -> `npm test`,","`build` -> `npm run build`, `start` -> `npm start`.","","## Future composition","","An agent runtime can later consume this Model Gateway port. Do not copy these","templates into an Agent Framework kit; compose against the port instead."])}function ua(){return h(["export function redactSecretShapedValues(value: string): string {"," let output = value;"," const liveKey = process.env.OPENROUTER_API_KEY;"," if (liveKey && liveKey.length > 0) {"," output = output.split(liveKey).join('[redacted]');"," }"," return output",' .replace(/(?:sk-or-[A-Za-z0-9_-]+|sk-[A-Za-z0-9_-]{8,})/gi, "[redacted]")',' .replace(/Bearer\\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")',` .replace(/authorization\\s*[:=]\\s*["\\']?[^"\\'\\s]+/gi, "Authorization: [redacted]")`,' .replace(/([?&](?:api[_-]?key|token|secret|password)=)[^&\\s]+/gi, "$1[redacted]");',"}","","function isHeaderMap(value: Headers | Record<string, string>): value is Headers {",' return typeof (value as Headers).forEach === "function" && typeof (value as Headers).get === "function";',"}","","export function redactHeaders(headers: Headers | Record<string, string>): Record<string, string> {"," const out: Record<string, string> = {};"," const assign = (key: string, value: string): void => {",' out[key] = key.toLowerCase() === "authorization" ? "[redacted]" : redactSecretShapedValues(value);'," };"," if (isHeaderMap(headers)) {"," headers.forEach((value, key) => assign(key, value));"," return out;"," }"," for (const [key, value] of Object.entries(headers)) {"," assign(key, value);"," }"," return out;","}"])}function fa(){return h(["import { redactHeaders, redactSecretShapedValues } from './redact.js';","","export type GatewayError = {"," message: string;"," statusCode?: number;"," requestId?: string;"," retryAfter?: string;"," safeHeaders?: Record<string, string>;","};","","export class GatewayConfigurationError extends Error {"," constructor(message: string) {"," super(message);",' this.name = "GatewayConfigurationError";'," }","}","","export class GatewayUpstreamError extends Error {"," readonly statusCode?: number;"," readonly requestId?: string;"," readonly retryAfter?: string;"," constructor(mapped: GatewayError) {",' const suffix = mapped.statusCode ? ` (status ${mapped.statusCode})` : "";',' const request = mapped.requestId ? ` request ${mapped.requestId}` : "";'," super(`${mapped.message}${suffix}${request}`);",' this.name = "GatewayUpstreamError";'," this.statusCode = mapped.statusCode;"," this.requestId = mapped.requestId;"," this.retryAfter = mapped.retryAfter;"," }","}","","function isRecord(value: unknown): value is Record<string, unknown> {",' return Boolean(value) && typeof value === "object";',"}","","function readHeaderMap(value: unknown): Record<string, string> | undefined {",' if (!value || typeof value !== "object") return undefined;',' if (typeof (value as Headers).forEach === "function" && typeof (value as Headers).get === "function") {'," return redactHeaders(value as Headers);"," }"," const out: Record<string, string> = {};"," for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {",' if (typeof entry === "string") out[key] = entry;'," }"," return Object.keys(out).length > 0 ? redactHeaders(out) : undefined;","}","","function headerLookup(headers: Record<string, string> | undefined, name: string): string | undefined {"," if (!headers) return undefined;"," const wanted = name.toLowerCase();"," for (const [key, value] of Object.entries(headers)) {"," if (key.toLowerCase() === wanted) return value;"," }"," return undefined;","}","","function numericStatus(value: unknown): number | undefined {",' return typeof value === "number" && Number.isInteger(value) ? value : undefined;',"}","","export function mapUpstreamError(error: unknown): GatewayError {"," const record = isRecord(error) ? error : {};"," const cause = isRecord(record.cause) ? record.cause : {};"," const httpMeta = isRecord(record.httpMeta)"," ? record.httpMeta"," : isRecord(record.HttpMeta)"," ? record.HttpMeta"," : {};"," const response = isRecord(httpMeta.Response)"," ? httpMeta.Response"," : isRecord(httpMeta.response)"," ? httpMeta.response"," : {};"," const statusCode ="," numericStatus(record.statusCode) ??"," numericStatus(record.status) ??"," numericStatus(record.status_code) ??"," numericStatus(cause.statusCode) ??"," numericStatus(response.status);"," const headers ="," readHeaderMap(record.headers) ??"," readHeaderMap(cause.headers) ??"," readHeaderMap(response.headers);"," const requestId =",' headerLookup(headers, "x-request-id") ??',' headerLookup(headers, "x-openrouter-request-id") ??',' (typeof record.requestId === "string" ? record.requestId : undefined);'," const rawMessage = error instanceof Error ? error.message : String(error);"," return {"," message: redactSecretShapedValues(rawMessage),"," statusCode,"," requestId,",' retryAfter: headerLookup(headers, "retry-after"),'," safeHeaders: headers,"," };","}","","export function toGatewayUpstreamError(error: unknown): GatewayUpstreamError {"," if (error instanceof GatewayUpstreamError) return error;"," return new GatewayUpstreamError(mapUpstreamError(error));","}"])}function ga(){return h(["export type ChatTurn = {",' role: "system" | "user" | "assistant";'," content: string;","};","","export type GenerateInput = {"," messages: ChatTurn[];"," signal?: AbortSignal;","};","","export type GenerateResult = {"," text: string;"," model?: string;"," id?: string;","};","","export interface ModelGateway {"," generate(input: GenerateInput): Promise<GenerateResult>;"," stream(input: GenerateInput): AsyncGenerator<string, void, void>;"," close(): Promise<void>;","}"])}function _a(){let e=we.map(i=>`"${i}"`).join(" | "),t=ke.map(i=>`"${i}"`).join(" | "),r=Ee.map(i=>`"${i}"`).join(" | "),o=at.map(i=>`"${i}"`).join(" | "),a=we.map(i=>`"${i}"`).join(", "),s=ke.map(i=>`"${i}"`).join(", "),n=Ee.map(i=>`"${i}"`).join(", "),l=nt.map(i=>`"${i}"`).join(", "),c=ot.map(i=>`"${i}"`).join(", ");return h(["import { readFileSync } from 'node:fs';","import { dirname, join } from 'node:path';","import { fileURLToPath } from 'node:url';","import type { ProviderPreferences } from '@openrouter/sdk/models/providerpreferences.js';","import { GatewayConfigurationError } from './errors.js';","","export { GatewayConfigurationError };","",`export const POLICY_FILE_NAME = ${JSON.stringify(de)};`,`export const DEFAULT_TIMEOUT_MS = ${3e4};`,`export const MAX_TIMEOUT_MS = ${6e5};`,`export type ProviderSort = ${e};`,`export type SortPartition = ${t};`,`export type Quantization = ${r};`,`export type DataCollection = ${o};`,`const SORT_VALUES = new Set<string>([${a}]);`,`const PARTITION_VALUES = new Set<string>([${s}]);`,`const QUANTIZATION_VALUES = new Set<string>([${n}]);`,`const MAX_PRICE_KEYS = new Set<string>([${l}]);`,`const PERCENTILE_KEYS = new Set<string>([${c}]);`,'const ROOT_KEYS = new Set(["models", "provider", "timeoutMs", "httpReferer", "appTitle"]);',"const PROVIDER_KEYS = new Set([",' "allowFallbacks",',' "order",',' "only",',' "ignore",',' "sort",',' "quantizations",',' "requireParameters",',' "zdr",',' "dataCollection",',' "enforceDistillableText",',' "maxPrice",',' "preferredMinThroughput",',' "preferredMaxLatency",',"]);","","export type MaxPrice = {"," prompt?: string;"," completion?: string;"," request?: string;"," image?: string;"," audio?: string;","};","","export type PercentileCutoffs = {"," p50?: number | null;"," p75?: number | null;"," p90?: number | null;"," p99?: number | null;","};","","export type ProviderSortConfig = {"," by?: ProviderSort | null;"," partition?: SortPartition | null;","};","","export type TypeScriptProviderPolicy = {"," allowFallbacks?: boolean | null;"," order?: string[] | null;"," only?: string[] | null;"," ignore?: string[] | null;"," sort?: ProviderSort | ProviderSortConfig | null;"," quantizations?: Quantization[] | null;"," requireParameters?: boolean | null;"," zdr?: boolean | null;"," dataCollection?: DataCollection | null;"," enforceDistillableText?: boolean | null;"," maxPrice?: MaxPrice | null;"," preferredMinThroughput?: number | PercentileCutoffs | null;"," preferredMaxLatency?: number | PercentileCutoffs | null;","};","","export type GatewayPolicyFile = {"," models?: string[];"," provider?: TypeScriptProviderPolicy | null;"," timeoutMs?: number | null;"," httpReferer?: string | null;"," appTitle?: string | null;","};","","export type GatewayConfig = {"," apiKey: string;"," model: string;"," models: string[];"," timeoutMs: number;"," httpReferer?: string;"," appTitle?: string;"," provider?: TypeScriptProviderPolicy;","};","","function isRecord(value: unknown): value is Record<string, unknown> {",' return Boolean(value) && typeof value === "object" && !Array.isArray(value);',"}","","function rejectUnknownKeys(record: Record<string, unknown>, allowed: Set<string>, label: string): void {"," const unknown = Object.keys(record).filter((key) => !allowed.has(key));"," if (unknown.length > 0) {",' throw new GatewayConfigurationError(`${label} contains unknown field(s): ${unknown.sort().join(", ")}`);'," }","}","","function optionalBoolean(value: unknown, label: string): boolean | null | undefined {"," if (value === undefined || value === null) return value;",' if (typeof value !== "boolean") {'," throw new GatewayConfigurationError(`${label} must be a boolean.`);"," }"," return value;","}","","function optionalString(value: unknown, label: string): string | undefined {",' if (value === undefined || value === null || value === "") return undefined;',' if (typeof value !== "string") {'," throw new GatewayConfigurationError(`${label} must be a string.`);"," }"," return value.trim();","}","","function asStringArray(value: unknown, label: string): string[] {"," if (value == null) return [];"," if (!Array.isArray(value)) {"," throw new GatewayConfigurationError(`${label} must be an array of non-empty strings.`);"," }"," const items = value.map((item, index) => {",' if (typeof item !== "string" || item.trim() === "") {'," throw new GatewayConfigurationError(`${label}[${index}] must be a non-empty string.`);"," }"," return item.trim();"," });"," const seen = new Set<string>();"," const duplicates: string[] = [];"," for (const item of items) {"," if (seen.has(item)) duplicates.push(item);"," seen.add(item);"," }"," if (duplicates.length > 0) {",' throw new GatewayConfigurationError(`${label} contains duplicates: ${[...new Set(duplicates)].join(", ")}`);'," }"," return items;","}","","function overlap(left: string[], right: string[]): string[] {"," const rightSet = new Set(right);"," return left.filter((item) => rightSet.has(item));","}","","function asPolicyInt(value: unknown, label: string): number {",' if (typeof value !== "number" || !Number.isInteger(value) || value <= 0 || value > MAX_TIMEOUT_MS) {'," throw new GatewayConfigurationError(`${label} must be a positive integer between 1 and ${MAX_TIMEOUT_MS}.`);"," }"," return value;","}","","function asEnvInt(value: string, label: string): number {"," if (!/^[1-9]\\d*$/.test(value.trim())) {"," throw new GatewayConfigurationError(`${label} must be a positive integer between 1 and ${MAX_TIMEOUT_MS}.`);"," }"," return asPolicyInt(Number(value.trim()), label);","}","","function asNonNegativeNumber(value: unknown, label: string): number {",' if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {'," throw new GatewayConfigurationError(`${label} must be a finite non-negative number.`);"," }"," return value;","}","","function asMaxPrice(value: unknown): MaxPrice | null | undefined {"," if (value === undefined || value === null) return value;"," if (!isRecord(value)) {",' throw new GatewayConfigurationError("provider.maxPrice must be an object.");'," }",' rejectUnknownKeys(value, MAX_PRICE_KEYS, "provider.maxPrice");'," const mapped: MaxPrice = {};",' for (const key of ["prompt", "completion", "request", "image", "audio"] as const) {'," const entry = value[key];"," if (entry === undefined) continue;",' if (typeof entry !== "string" || !/^(?:0|[1-9]\\d*)(?:\\.\\d+)?$/.test(entry) || !Number.isFinite(Number(entry)) || Number(entry) < 0) {'," throw new GatewayConfigurationError(`provider.maxPrice.${key} must be a non-negative numeric string.`);"," }"," mapped[key] = entry;"," }"," return mapped;","}","","function asPercentileOrNumber(value: unknown, label: string): number | PercentileCutoffs | null | undefined {"," if (value === undefined || value === null) return value;",' if (typeof value === "number") return asNonNegativeNumber(value, label);'," if (!isRecord(value)) {"," throw new GatewayConfigurationError(`${label} must be a number or percentile object.`);"," }"," rejectUnknownKeys(value, PERCENTILE_KEYS, label);"," const mapped: PercentileCutoffs = {};",' for (const key of ["p50", "p75", "p90", "p99"] as const) {'," const entry = value[key];"," if (entry === undefined) continue;"," mapped[key] = entry === null ? null : asNonNegativeNumber(entry, `${label}.${key}`);"," }"," return mapped;","}","","function asSort(value: unknown): ProviderSort | ProviderSortConfig | null | undefined {"," if (value === undefined || value === null) return value;",' if (typeof value === "string") {'," if (!SORT_VALUES.has(value)) {",' throw new GatewayConfigurationError("provider.sort must be price, throughput, latency, or exacto.");'," }"," return value as ProviderSort;"," }"," if (!isRecord(value)) {",' throw new GatewayConfigurationError("provider.sort must be a string or { by, partition } object.");'," }",' rejectUnknownKeys(value, new Set(["by", "partition"]), "provider.sort");'," const by = value.by;"," const partition = value.partition;",' if (by != null && (typeof by !== "string" || !SORT_VALUES.has(by))) {',' throw new GatewayConfigurationError("provider.sort.by must be price, throughput, latency, or exacto.");'," }",' if (partition != null && (typeof partition !== "string" || !PARTITION_VALUES.has(partition))) {',' throw new GatewayConfigurationError("provider.sort.partition must be model or none.");'," }"," return {"," by: by == null ? by : (by as ProviderSort),"," partition: partition == null ? partition : (partition as SortPartition),"," };","}","","function asQuantizations(value: unknown): Quantization[] | null | undefined {"," if (value === undefined || value === null) return value;",' const items = asStringArray(value, "provider.quantizations");'," for (const item of items) {"," if (!QUANTIZATION_VALUES.has(item)) {"," throw new GatewayConfigurationError(`unsupported provider.quantizations value: ${item}`);"," }"," }"," return items as Quantization[];","}","","export function projectRootFromModule(moduleUrl: string): string {"," return dirname(fileURLToPath(moduleUrl));","}","","export function loadPolicyFile(root: string): unknown {"," const policyPath = join(root, POLICY_FILE_NAME);"," try {",' return JSON.parse(readFileSync(policyPath, "utf8")) as unknown;'," } catch (error) {"," throw new GatewayConfigurationError("," `Unable to read ${POLICY_FILE_NAME}: ${error instanceof Error ? error.message : String(error)}`"," );"," }","}","","function parsePolicy(value: unknown): GatewayPolicyFile {"," if (!isRecord(value)) {",' throw new GatewayConfigurationError("gateway.policy.json must be a JSON object.");'," }",' rejectUnknownKeys(value, ROOT_KEYS, "gateway.policy.json");',' const models = value.models === undefined ? undefined : asStringArray(value.models, "models");'," let provider: TypeScriptProviderPolicy | null | undefined;"," if (value.provider === undefined || value.provider === null) {"," provider = value.provider;"," } else {"," if (!isRecord(value.provider)) {",' throw new GatewayConfigurationError("provider must be an object.");'," }",' rejectUnknownKeys(value.provider, PROVIDER_KEYS, "provider");'," const dataCollection = value.provider.dataCollection;",' if (dataCollection != null && dataCollection !== "allow" && dataCollection !== "deny") {',' throw new GatewayConfigurationError("provider.dataCollection must be allow or deny.");'," }"," provider = {",' allowFallbacks: optionalBoolean(value.provider.allowFallbacks, "provider.allowFallbacks"),',' requireParameters: optionalBoolean(value.provider.requireParameters, "provider.requireParameters"),',' zdr: optionalBoolean(value.provider.zdr, "provider.zdr"),',' enforceDistillableText: optionalBoolean(value.provider.enforceDistillableText, "provider.enforceDistillableText"),'," dataCollection: dataCollection as DataCollection | null | undefined,",' only: value.provider.only == null ? value.provider.only : asStringArray(value.provider.only, "provider.only"),',' ignore: value.provider.ignore == null ? value.provider.ignore : asStringArray(value.provider.ignore, "provider.ignore"),',' order: value.provider.order == null ? value.provider.order : asStringArray(value.provider.order, "provider.order"),'," sort: asSort(value.provider.sort),"," quantizations: asQuantizations(value.provider.quantizations),"," maxPrice: asMaxPrice(value.provider.maxPrice),",' preferredMinThroughput: asPercentileOrNumber(value.provider.preferredMinThroughput, "provider.preferredMinThroughput"),',' preferredMaxLatency: asPercentileOrNumber(value.provider.preferredMaxLatency, "provider.preferredMaxLatency"),'," };"," }"," return {"," models,"," provider,",' timeoutMs: value.timeoutMs === undefined || value.timeoutMs === null ? value.timeoutMs : asPolicyInt(value.timeoutMs, "timeoutMs"),',' httpReferer: optionalString(value.httpReferer, "httpReferer") ?? null,',' appTitle: optionalString(value.appTitle, "appTitle") ?? null,'," };","}","","export function loadGatewayConfig(input: {"," env?: NodeJS.ProcessEnv;"," policy?: unknown;"," root?: string;","} = {}): GatewayConfig {"," const env = input.env ?? process.env;"," const policy = parsePolicy(",' input.policy ?? loadPolicyFile(input.root ?? join(projectRootFromModule(import.meta.url), "..", ".."))'," );",' const apiKey = env.OPENROUTER_API_KEY?.trim() ?? "";',' const model = env.OPENROUTER_MODEL?.trim() ?? "";'," if (!apiKey) {",' throw new GatewayConfigurationError("OPENROUTER_API_KEY is required before any OpenRouter request.");'," }"," if (!model) {",' throw new GatewayConfigurationError("OPENROUTER_MODEL is required. Choose a model slug; this project does not default one.");'," }"," const models = policy.models ?? [];"," if (models.includes(model)) {",' throw new GatewayConfigurationError("models fallbacks must not repeat OPENROUTER_MODEL.");'," }"," const provider = policy.provider ?? undefined;"," const only = provider?.only ?? [];"," const ignore = provider?.ignore ?? [];"," const order = provider?.order ?? [];"," const denied = overlap(only, ignore);"," if (denied.length > 0) {",' throw new GatewayConfigurationError(`provider.only and provider.ignore overlap: ${denied.join(", ")}`);'," }"," const orderedIgnored = overlap(order, ignore);"," if (orderedIgnored.length > 0) {",' throw new GatewayConfigurationError(`provider.order includes ignored providers: ${orderedIgnored.join(", ")}`);'," }",' if (provider?.zdr === true && provider.dataCollection === "allow") {',' throw new GatewayConfigurationError("provider.zdr=true cannot be combined with dataCollection=allow.");'," }"," const timeoutRaw = env.OPENROUTER_TIMEOUT_MS?.trim();"," const timeoutMs = timeoutRaw",' ? asEnvInt(timeoutRaw, "OPENROUTER_TIMEOUT_MS")'," : policy.timeoutMs == null"," ? DEFAULT_TIMEOUT_MS",' : asPolicyInt(policy.timeoutMs, "timeoutMs");'," const httpReferer = env.OPENROUTER_HTTP_REFERER?.trim() || policy.httpReferer || undefined;"," const appTitle = env.OPENROUTER_APP_TITLE?.trim() || policy.appTitle || undefined;"," return {"," apiKey,"," model,"," models,"," timeoutMs,"," httpReferer: httpReferer || undefined,"," appTitle: appTitle || undefined,"," provider,"," };","}","","export function toSdkProvider(provider: TypeScriptProviderPolicy | undefined): ProviderPreferences | undefined {"," if (!provider) return undefined;"," const mapped: ProviderPreferences = {};"," if (provider.allowFallbacks != null) mapped.allowFallbacks = provider.allowFallbacks;"," if (provider.order && provider.order.length > 0) mapped.order = provider.order;"," if (provider.only && provider.only.length > 0) mapped.only = provider.only;"," if (provider.ignore && provider.ignore.length > 0) mapped.ignore = provider.ignore;"," if (provider.sort != null) mapped.sort = provider.sort;"," if (provider.quantizations && provider.quantizations.length > 0) mapped.quantizations = provider.quantizations;"," if (provider.requireParameters != null) mapped.requireParameters = provider.requireParameters;"," if (provider.zdr != null) mapped.zdr = provider.zdr;"," if (provider.dataCollection != null) mapped.dataCollection = provider.dataCollection;"," if (provider.enforceDistillableText != null) mapped.enforceDistillableText = provider.enforceDistillableText;"," if (provider.maxPrice) mapped.maxPrice = provider.maxPrice;"," if (provider.preferredMinThroughput != null) mapped.preferredMinThroughput = provider.preferredMinThroughput;"," if (provider.preferredMaxLatency != null) mapped.preferredMaxLatency = provider.preferredMaxLatency;"," return Object.keys(mapped).length > 0 ? mapped : undefined;","}"])}function ya(){return h(["import { HTTPClient, OpenRouter } from '@openrouter/sdk';","import { loadGatewayConfig, toSdkProvider, type GatewayConfig } from './config.js';","import { GatewayConfigurationError, toGatewayUpstreamError } from './errors.js';","import type { GenerateInput, GenerateResult, ModelGateway } from './port.js';","","export type OpenRouterGatewayOptions = {"," config?: GatewayConfig;"," httpClient?: HTTPClient;","};","","function asText(value: unknown): string {",' if (typeof value === "string") return value;',' if (Array.isArray(value)) return value.map((part) => asText(part)).join("");',' if (value && typeof value === "object" && "text" in value) {'," return asText((value as { text?: unknown }).text);"," }",' return "";',"}","","function isCancellableError(error: unknown): boolean {"," if (!(error instanceof Error)) return false;"," return (",' error.name === "AbortError" ||',' error.name === "TimeoutError" ||',' error.name === "RequestAbortedError" ||',' error.name === "RequestTimeoutError"'," );","}","","// Pinned @openrouter/sdk EventStream extends ReadableStream and has no close().","// Cancellation is async iterator return() -> ReadableStream.cancel(). If the","// stream already completed or errored, cancel() may be a no-op; the gateway","// still drops the reference and remains usable for a later request.","const releasedStreams = new WeakSet<object>();","","async function releaseOpenedStream(stream: unknown, iterator?: AsyncIterator<unknown>): Promise<void> {",' if (!stream || typeof stream !== "object" || releasedStreams.has(stream)) return;'," releasedStreams.add(stream);"," try {",' if (iterator && typeof iterator.return === "function") {'," await iterator.return(undefined);"," return;"," }"," const closer = (stream as { close?: () => unknown }).close;",' if (typeof closer === "function") {'," await closer.call(stream);"," return;"," }"," const cancel = (stream as { cancel?: (reason?: unknown) => unknown }).cancel;",' if (typeof cancel === "function") {'," await cancel.call(stream);"," }"," } catch {"," // Idempotent cleanup must not hide the original error."," }","}","","export class OpenRouterModelGateway implements ModelGateway {"," private readonly config: GatewayConfig;"," private readonly client: OpenRouter;"," private closed = false;",""," constructor(options: OpenRouterGatewayOptions = {}) {"," this.config = options.config ?? loadGatewayConfig();"," this.client = new OpenRouter({"," apiKey: this.config.apiKey,"," httpReferer: this.config.httpReferer,"," appTitle: this.config.appTitle,"," timeoutMs: this.config.timeoutMs,",' retryConfig: { strategy: "none" },'," httpClient: options.httpClient,"," });"," }",""," async generate(input: GenerateInput): Promise<GenerateResult> {"," this.assertOpen();"," this.assertMessages(input.messages);"," try {"," const completion = await this.client.chat.send("," {"," chatRequest: {"," model: this.config.model,"," messages: input.messages,"," models: this.config.models.length > 0 ? this.config.models : undefined,"," provider: toSdkProvider(this.config.provider),"," stream: false,"," },"," },"," { timeoutMs: this.config.timeoutMs, signal: input.signal }"," );"," if (completion instanceof ReadableStream) {",' throw new GatewayConfigurationError("Expected a non-streaming OpenRouter response.");'," }"," const payload = completion as {"," id?: string;"," model?: string;"," choices?: Array<{ message?: { content?: unknown } }>;"," };"," return {"," text: asText(payload.choices?.[0]?.message?.content),"," model: payload.model,"," id: payload.id,"," };"," } catch (error) {"," throw this.normalizeCaught(error);"," }"," }",""," async *stream(input: GenerateInput): AsyncGenerator<string, void, void> {"," this.assertOpen();"," this.assertMessages(input.messages);"," let opened: unknown;"," let iterator: AsyncIterator<unknown> | undefined;"," try {"," opened = await this.client.chat.send("," {"," chatRequest: {"," model: this.config.model,"," messages: input.messages,"," models: this.config.models.length > 0 ? this.config.models : undefined,"," provider: toSdkProvider(this.config.provider),"," stream: true,"," },"," },"," { timeoutMs: this.config.timeoutMs, signal: input.signal }"," );",' if (opened && typeof opened === "object" && Symbol.asyncIterator in opened) {'," iterator = (opened as AsyncIterable<unknown>)[Symbol.asyncIterator]();"," } else if (opened instanceof ReadableStream) {"," const reader = opened.getReader();"," iterator = {"," next: () => reader.read(),"," return: async () => {"," try {"," await reader.cancel();"," } catch {"," // Reader cancel is best-effort."," }"," reader.releaseLock();"," return { done: true, value: undefined };"," },"," };"," } else {"," return;"," }"," for (;;) {"," const step = await iterator.next();"," if (step.done) break;"," const value = step.value as { choices?: Array<{ delta?: { content?: unknown } }> } | string;",' const text = typeof value === "string" ? value : asText(value.choices?.[0]?.delta?.content);'," if (text) yield text;"," }"," } catch (error) {"," throw this.normalizeCaught(error);"," } finally {"," await releaseOpenedStream(opened, iterator);"," }"," }",""," async close(): Promise<void> {"," if (this.closed) return;"," this.closed = true;"," const closer = (this.client as { close?: () => unknown }).close;",' if (typeof closer === "function") {'," await closer.call(this.client);"," }"," }",""," private assertOpen(): void {"," if (this.closed) {",' throw new GatewayConfigurationError("OpenRouterModelGateway is closed.");'," }"," }","",' private assertMessages(messages: GenerateInput["messages"]): void {'," if (!Array.isArray(messages) || messages.length === 0) {",' throw new GatewayConfigurationError("messages must not be empty.");'," }"," }",""," private normalizeCaught(error: unknown): Error {"," if (error instanceof GatewayConfigurationError) return error;"," if (isCancellableError(error)) return error as Error;"," return toGatewayUpstreamError(error);"," }","}"])}function ha(){return h(["import { HTTPClient } from '@openrouter/sdk';","import { loadGatewayConfig } from './config.js';","import { OpenRouterModelGateway } from './openrouter-gateway.js';","import { GatewayConfigurationError, mapUpstreamError } from './errors.js';","","function promptFromArgv(): string {",' const args = process.argv.slice(2).filter((arg) => arg !== "--smoke");',' return args.join(" ").trim() || "Reply with a short acknowledgement.";',"}","",'function chatCompletionPayload(content: string, model = "openrouter/test-model"): string {'," return JSON.stringify({",' id: "smoke-non-stream",'," created: 1,"," model,",' object: "chat.completion",'," system_fingerprint: null,",' choices: [{ finish_reason: "stop", index: 0, message: { role: "assistant", content } }],'," });","}","","function chatStreamSse(parts: string[]): string {"," return ("," parts"," .map((content, index) =>"," `data: ${JSON.stringify({",' id: "smoke-stream",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content }, finish_reason: index === parts.length - 1 ? "stop" : null, index: 0 }],'," })}\\n\\n`"," )",' .join("") + "data: [DONE]\\n\\n"'," );","}","","function smokeClient(): HTTPClient {"," return new HTTPClient({"," fetcher: async (input, init) => {"," const request = input instanceof Request ? input : new Request(input, init);"," const body = await request.text();"," const parsed = JSON.parse(body) as { stream?: boolean; model?: string };"," if (parsed.stream) {",' return new Response(chatStreamSse(["smoke"]), {'," status: 200,",' headers: { "content-type": "text/event-stream", "x-request-id": "smoke-stream" },'," });"," }",' return new Response(chatCompletionPayload("smoke-ok", parsed.model), {'," status: 200,",' headers: { "content-type": "application/json", "x-request-id": "smoke-id" },'," });"," },"," });","}","","async function runSmoke(): Promise<void> {",' process.env.OPENROUTER_API_KEY ??= "test-openrouter-key";',' process.env.OPENROUTER_MODEL ??= "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig(),"," httpClient: smokeClient(),"," });"," const generated = await gateway.generate({",' messages: [{ role: "user", content: "ping" }],'," });",' let streamed = "";',' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "ping" }] })) {'," streamed += chunk;"," }"," await gateway.close();"," process.stdout.write(`${generated.text}:${streamed}\\n`);","}","","async function main(): Promise<void> {",' if (process.argv.includes("--smoke")) {'," await runSmoke();"," return;"," }"," const gateway = new OpenRouterModelGateway();"," try {"," const controller = new AbortController();"," const timeout = AbortSignal.timeout(loadGatewayConfig().timeoutMs);",' timeout.addEventListener("abort", () => controller.abort());'," let wrote = false;"," for await (const chunk of gateway.stream({",' messages: [{ role: "user", content: promptFromArgv() }],'," signal: controller.signal,"," })) {"," process.stdout.write(chunk);"," wrote = true;"," }"," if (!wrote) {"," const result = await gateway.generate({",' messages: [{ role: "user", content: promptFromArgv() }],'," signal: controller.signal,"," });"," process.stdout.write(result.text);"," }",' process.stdout.write("\\n");'," } finally {"," await gateway.close();"," }","}","","main().catch((error: unknown) => {"," if (error instanceof GatewayConfigurationError) {"," process.stderr.write(`${error.message}\\n`);"," } else {"," const mapped = mapUpstreamError(error);"," process.stderr.write(`${mapped.message}\\n`);"," }"," process.exitCode = 1;","});"])}function wa(){return h(["export { loadGatewayConfig, toSdkProvider } from './config.js';","export { OpenRouterModelGateway } from './openrouter-gateway.js';","export { redactSecretShapedValues } from './redact.js';","export { mapUpstreamError, GatewayConfigurationError, GatewayUpstreamError } from './errors.js';","export type { ModelGateway } from './port.js';"])}function ka(){return h(["import assert from 'node:assert/strict';","import { mkdtempSync, writeFileSync } from 'node:fs';","import { readFile } from 'node:fs/promises';","import { tmpdir } from 'node:os';","import { dirname, join } from 'node:path';","import { test } from 'node:test';","import { fileURLToPath } from 'node:url';","","import { HTTPClient } from '@openrouter/sdk';","","import { GatewayConfigurationError, loadGatewayConfig, loadPolicyFile, toSdkProvider } from '../src/config.js';","import { GatewayUpstreamError, mapUpstreamError } from '../src/errors.js';","import { OpenRouterModelGateway } from '../src/openrouter-gateway.js';","import { redactSecretShapedValues } from '../src/redact.js';","",'const root = join(dirname(fileURLToPath(import.meta.url)), "..", "..");',`const PARITY_CASES = ${JSON.stringify(it)} as const;`,"","function fakeClient(handler: (request: Request) => Promise<Response> | Response): HTTPClient {"," return new HTTPClient({"," fetcher: async (input, init) => {"," const request = input instanceof Request ? input : new Request(input, init);"," return handler(request);"," },"," });","}","",'function chatCompletionPayload(content: string, model = "openrouter/test-model"): string {'," return JSON.stringify({",' id: "gen-1",'," created: 1,"," model,",' object: "chat.completion",'," system_fingerprint: null,",' choices: [{ finish_reason: "stop", index: 0, message: { role: "assistant", content } }],'," });","}","","function chatStreamSse(parts: string[]): string {"," return ("," parts"," .map((content, index) =>"," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content }, finish_reason: index === parts.length - 1 ? "stop" : null, index: 0 }],'," })}\\n\\n`"," )",' .join("") + "data: [DONE]\\n\\n"'," );","}","",'test("redacts keys, authorization, and url secrets", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-live-secret";'," const raw =",' "Authorization: Bearer sk-or-live-secret called https://example.test/v1?api_key=sk-or-live-secret";'," const redacted = redactSecretShapedValues(raw);",' assert.equal(redacted.includes("sk-or-live-secret"), false);'," assert.match(redacted, /\\[redacted\\]/);","});","",'test("configuration fails before a network request", async () => {'," const previousKey = process.env.OPENROUTER_API_KEY;"," const previousModel = process.env.OPENROUTER_MODEL;"," delete process.env.OPENROUTER_API_KEY;",' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," try {"," assert.throws(() => loadGatewayConfig({ root }), GatewayConfigurationError);"," } finally {"," if (previousKey === undefined) delete process.env.OPENROUTER_API_KEY;"," else process.env.OPENROUTER_API_KEY = previousKey;"," if (previousModel === undefined) delete process.env.OPENROUTER_MODEL;"," else process.env.OPENROUTER_MODEL = previousModel;"," }","});","",'test("rejects overlapping provider allow and deny lists", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," assert.throws("," () =>"," loadGatewayConfig({",' policy: { provider: { only: ["openai"], ignore: ["openai"] } },'," }),"," /overlap/"," );","});","",'test("maps TypeScript SDK provider field names", () => {'," const mapped = toSdkProvider({"," allowFallbacks: false,"," zdr: true,",' dataCollection: "deny",',' sort: "latency",',' order: ["anthropic"],'," });"," assert.deepEqual(mapped, {"," allowFallbacks: false,"," zdr: true,",' dataCollection: "deny",',' sort: "latency",',' order: ["anthropic"],'," });","});","",'test("non-stream generation uses injected transport and records policy", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";',' let body = "";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({",' policy: { provider: { zdr: true, dataCollection: "deny" }, models: ["openrouter/fallback"] },'," }),"," httpClient: fakeClient(async (request) => {"," body = await request.text();",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }),"," });",' const result = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(result.text, "ok");'," const parsed = JSON.parse(body) as {"," model: string;"," models?: string[];"," provider?: { zdr?: boolean; data_collection?: string; dataCollection?: string };"," };",' assert.equal(parsed.model, "openrouter/test-model");',' assert.deepEqual(parsed.models, ["openrouter/fallback"]);'," assert.equal(parsed.provider?.zdr, true);",' assert.equal(parsed.provider?.data_collection ?? parsed.provider?.dataCollection, "deny");'," await gateway.close();","});","",'test("streaming consumes chunks and cancels the reader", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async () => {",' return new Response(chatStreamSse(["hel", "lo"]), {'," status: 200,",' headers: { "content-type": "text/event-stream" },'," });"," }),"," });",' let text = "";',' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," text += chunk;"," }",' assert.equal(text.includes("hel") || text.includes("hello"), true);'," await gateway.close();","});","",'test("maps upstream status and request id without leaking secrets", () => {'," const headers = new Headers({",' authorization: "Bearer sk-or-secret",',' "x-request-id": "req-123",',' "retry-after": "2",'," });"," const mapped = mapUpstreamError({",' message: "Authorization: Bearer sk-or-secret failed",'," statusCode: 429,"," headers,"," });"," assert.equal(mapped.statusCode, 429);",' assert.equal(mapped.requestId, "req-123");',' assert.equal(mapped.retryAfter, "2");',' assert.equal(mapped.message.includes("sk-or-secret"), false);',' assert.equal(mapped.safeHeaders?.authorization ?? mapped.safeHeaders?.Authorization, "[redacted]");',"});","",'test("timeout uses AbortSignal", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: { ...loadGatewayConfig({ policy: {} }), timeoutMs: 20 },"," httpClient: fakeClient(async (request) => {"," const signal = request.signal;"," await new Promise((_, reject) => {",' const fail = () => reject(signal.reason ?? new Error("aborted"));'," const watchdog = setTimeout(() => {",' reject(new Error("AbortSignal was not forwarded to transport"));'," }, 1000);"," const abort = () => {"," clearTimeout(watchdog);"," fail();"," };"," if (signal.aborted) {"," abort();"," return;"," }",' signal.addEventListener("abort", abort, { once: true });'," });",' return new Response("{}", { status: 200 });'," }),"," });"," await assert.rejects(",' () => gateway.generate({ messages: [{ role: "user", content: "hi" }] }),'," (error: unknown) => {"," const text = error instanceof Error ? `${error.name}: ${error.message}` : String(error);"," return /timed out|aborted by client|AbortError|TimeoutError|This operation was aborted/i.test(text);"," }"," );"," const cancelled = new AbortController();"," cancelled.abort();"," await assert.rejects(() =>"," gateway.generate({",' messages: [{ role: "user", content: "hi" }],'," signal: cancelled.signal,"," })"," );"," await gateway.close();","});","",'test("timeoutMs is loaded from OPENROUTER_TIMEOUT_MS", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";',' process.env.OPENROUTER_TIMEOUT_MS = "1500";'," const config = loadGatewayConfig({ policy: {} });"," assert.equal(config.timeoutMs, 1500);"," delete process.env.OPENROUTER_TIMEOUT_MS;","});","",'test("policy file stays source-controlled and secret-free", async () => {',' const policy = await readFile(join(root, "gateway.policy.json"), "utf8");',' assert.equal(policy.includes("sk-"), false);',' assert.equal(policy.includes("OPENROUTER_API_KEY"), false);',"});","",'test("rejects every invalid policy category before transport", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const cases: Array<[string, unknown, RegExp]> = [",' ["root array", [], /JSON object/],',' ["unknown root field", { widget: true }, /unknown field/],',' ["models not array", { models: "openai/gpt" }, /models must be an array/],',' ["empty model", { models: [" "] }, /non-empty string/],',' ["duplicate models", { models: ["a", "a"] }, /duplicates/],',' ["primary in fallbacks", { models: ["openrouter/test-model"] }, /repeat OPENROUTER_MODEL/],',' ["provider string", { provider: "openai" }, /provider must be an object/],',' ["unknown provider field", { provider: { fallbacks: true } }, /unknown field/],',' ["allowFallbacks string", { provider: { allowFallbacks: "yes" } }, /boolean/],',' ["requireParameters number", { provider: { requireParameters: 1 } }, /boolean/],',' ["zdr string", { provider: { zdr: "true" } }, /boolean/],',' ["only ignore overlap", { provider: { only: ["openai"], ignore: ["openai"] } }, /overlap/],',' ["order includes ignore", { provider: { order: ["openai"], ignore: ["openai"] } }, /ignored providers/],',' ["dataCollection invalid", { provider: { dataCollection: "never" } }, /allow or deny/],',' ["sort invalid", { provider: { sort: "cheap" } }, /sort/],',' ["sort partition invalid", { provider: { sort: { by: "price", partition: "all" } } }, /partition/],',' ["quantization invalid", { provider: { quantizations: ["int5"] } }, /quantizations/],',' ["maxPrice number", { provider: { maxPrice: { prompt: 1 } } }, /numeric string/],',' ["maxPrice unknown key", { provider: { maxPrice: { tokens: "1" } } }, /unknown field/],',' ["maxPrice negative", { provider: { maxPrice: { prompt: "-1" } } }, /numeric string/],',' ["maxPrice malformed", { provider: { maxPrice: { prompt: "not-a-price" } } }, /numeric string/],',' ["maxPrice overflow", { provider: { maxPrice: { prompt: "9".repeat(400) } } }, /numeric string/],',' ["percentile unknown", { provider: { preferredMaxLatency: { p95: 1 } } }, /unknown field/],',' ["percentile negative", { provider: { preferredMinThroughput: { p50: -1 } } }, /non-negative/],',' ["timeout float", { timeoutMs: 30.5 }, /positive integer/],',' ["timeout zero", { timeoutMs: 0 }, /positive integer/],',' ["timeout too large", { timeoutMs: 600001 }, /positive integer/],',' ["timeout string", { timeoutMs: "30000" }, /positive integer/],',' ["attribution number", { httpReferer: 1 }, /string/],',' ["zdr with allow", { provider: { zdr: true, dataCollection: "allow" } }, /zdr/],'," ];"," for (const [name, policy, pattern] of cases) {"," assert.throws("," () => loadGatewayConfig({ policy }),"," (error: unknown) => {"," assert.equal(error instanceof GatewayConfigurationError, true, name);"," assert.match((error as Error).message, pattern, name);"," return true;"," }"," );"," }","});","",'test("accepts official provider policy boundary values", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const config = loadGatewayConfig({"," policy: {",' models: ["openrouter/fallback"],'," timeoutMs: 1,",' httpReferer: "https://example.test",',' appTitle: "gateway",'," provider: {"," allowFallbacks: false,"," requireParameters: true,"," zdr: true,",' dataCollection: "deny",'," enforceDistillableText: true,",' only: ["anthropic"],',' ignore: ["openai"],',' order: ["anthropic"],',' sort: { by: "exacto", partition: "none" },',' quantizations: ["fp16"],',' maxPrice: { prompt: "0", completion: "1.5", request: "2", image: "3", audio: "4" },'," preferredMinThroughput: { p50: 0, p75: 1, p90: 2, p99: 3 },"," preferredMaxLatency: 1.5,"," },"," },"," });"," assert.equal(config.timeoutMs, 1);"," const mapped = toSdkProvider(config.provider);"," assert.equal(mapped?.enforceDistillableText, true);",' assert.deepEqual(mapped?.sort, { by: "exacto", partition: "none" });',' assert.equal(mapped?.maxPrice?.prompt, "0");'," const upper = loadGatewayConfig({ policy: { timeoutMs: 600000 } });"," assert.equal(upper.timeoutMs, 600000);","});","",'test("policy parity corpus executes the generated validator", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," for (const entry of PARITY_CASES) {",' if (entry.decision === "reject") {'," assert.throws("," () => loadGatewayConfig({ policy: entry.typescript }),"," (error: unknown) => {"," assert.equal(error instanceof GatewayConfigurationError, true, entry.id);"," if (entry.needle) assert.match((error as Error).message, new RegExp(entry.needle), entry.id);"," return true;"," }"," );"," } else {"," const config = loadGatewayConfig({ policy: entry.typescript });"," const mapped = toSdkProvider(config.provider);",' if (entry.id === "max-price-zero") assert.equal(mapped?.maxPrice?.prompt, "0");',' if (entry.id === "max-price-micro") assert.equal(mapped?.maxPrice?.prompt, "0.000001");',' if (entry.id === "max-price-positive") assert.equal(mapped?.maxPrice?.completion, "1.5");'," }"," }","});","",'test("finite maxPrice strings are preserved or rejected", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const accepted = loadGatewayConfig({",' policy: { provider: { maxPrice: { prompt: "0", completion: "0.000001", request: "12.5" } } },'," });"," const mapped = toSdkProvider(accepted.provider)?.maxPrice;",' assert.equal(mapped?.prompt, "0");',' assert.equal(mapped?.completion, "0.000001");',' assert.equal(mapped?.request, "12.5");',' for (const value of ["-1", "not-a-price", "9".repeat(400), "1e6", "+1", "inf", "nan"]) {'," assert.throws("," () => loadGatewayConfig({ policy: { provider: { maxPrice: { prompt: value } } } }),"," GatewayConfigurationError"," );"," }"," assert.throws(",' () => loadGatewayConfig({ policy: { provider: { maxPrice: { tokens: "1" } } } }),'," GatewayConfigurationError"," );","});","",'test("rejects non-finite numeric policy values", () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," assert.throws("," () => loadGatewayConfig({ policy: { provider: { preferredMaxLatency: Number.POSITIVE_INFINITY } } }),"," GatewayConfigurationError"," );"," assert.throws("," () => loadGatewayConfig({ policy: { provider: { preferredMinThroughput: Number.NaN } } }),"," GatewayConfigurationError"," );"," assert.throws("," () => loadGatewayConfig({ policy: { provider: { preferredMaxLatency: { p50: Number.NEGATIVE_INFINITY } } } }),"," GatewayConfigurationError"," );","});","",'test("missing policy file is a configuration error", () => {',' const dir = mkdtempSync(join(tmpdir(), "or-missing-policy-"));'," assert.throws(() => loadPolicyFile(dir), GatewayConfigurationError);","});","",'test("malformed policy JSON is a configuration error", () => {',' const dir = mkdtempSync(join(tmpdir(), "or-policy-"));',' writeFileSync(join(dir, "gateway.policy.json"), "{");'," assert.throws(() => loadPolicyFile(dir), GatewayConfigurationError);","});","",'test("empty messages fail before transport", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," let called = false;"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async () => {"," called = true;",' return new Response(chatCompletionPayload("nope"), { status: 200, headers: { "content-type": "application/json" } });'," }),"," });"," await assert.rejects(() => gateway.generate({ messages: [] }), GatewayConfigurationError);"," await assert.rejects(() => gateway.stream({ messages: [] }).next(), GatewayConfigurationError);"," assert.equal(called, false);"," await gateway.close();","});","",'test("empty successful completion returns an empty string", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),",' httpClient: fakeClient(async () => new Response(chatCompletionPayload(""), {'," status: 200,",' headers: { "content-type": "application/json" },'," })),"," });",' const result = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(result.text, "");'," await gateway.close();","});","",'test("close is idempotent and then rejects new work", async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),",' httpClient: fakeClient(async () => new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," })),"," });"," await gateway.close();"," await gateway.close();",' await assert.rejects(() => gateway.generate({ messages: [{ role: "user", content: "hi" }] }), GatewayConfigurationError);',"});","",'test("stream-open failure is a redacted upstream error with metadata", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async () =>",' new Response(JSON.stringify({ error: { message: "Authorization: Bearer sk-or-secret" } }), {'," status: 401,"," headers: {",' "content-type": "application/json",',' "x-request-id": "req-open",',' "retry-after": "7",',' authorization: "Bearer sk-or-secret",'," },"," })"," ),"," });"," await assert.rejects(",' () => gateway.stream({ messages: [{ role: "user", content: "hi" }] }).next(),'," (error: unknown) => {"," assert.equal(error instanceof GatewayUpstreamError, true);"," const upstream = error as GatewayUpstreamError;"," assert.equal(upstream.statusCode, 401);",' assert.equal(upstream.requestId, "req-open");',' assert.equal(upstream.retryAfter, "7");',' assert.equal(upstream.message.includes("sk-or-secret"), false);'," return true;"," }"," );"," await gateway.close();","});","",'test("mid-stream failure is normalized, redacted, and cleaned up", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: null, index: 0 }],'," })}\\n\\n`"," )"," );",' controller.error(new Error("Authorization: Bearer sk-or-secret mid-stream"));'," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });"," await assert.rejects("," async () => {",' for await (const _chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," void _chunk;"," }"," },"," (error: unknown) => {"," const text = error instanceof Error ? error.message : String(error);",' assert.equal(text.includes("sk-or-secret"), false);'," assert.equal(error instanceof GatewayConfigurationError, false);"," return /mid-stream|redacted|Unexpected|stream/i.test(text) || error instanceof GatewayUpstreamError;"," }"," );",' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("consumer early stream termination closes the upstream body", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," let cancelled = false;"," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: null, index: 0 }],'," })}\\n\\n`"," )"," );"," },"," cancel() {"," cancelled = true;"," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });",' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {',' assert.equal(chunk.includes("hel"), true);'," break;"," }"," assert.equal(cancelled, true);",' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("normal stream completion stays successful and leaves the gateway reusable", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," let cancelCount = 0;"," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: "stop", index: 0 }],'," })}\\n\\n`"," )"," );",' controller.enqueue(encoder.encode("data: [DONE]\\n\\n"));'," controller.close();"," },"," cancel() {"," cancelCount += 1;"," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });"," const chunks: string[] = [];",' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," chunks.push(chunk);"," }",' assert.equal(chunks.join("").includes("hel"), true);'," // Pinned EventStream closes a completed ReadableStream without calling cancel()."," // Iterator.return() is still invoked once; it is a no-op on an already-closed stream."," assert.ok(cancelCount === 0 || cancelCount === 1);",' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("cleanup failure does not mask upstream or convert success into an error", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const encoder = new TextEncoder();"," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," const raw = await request.text();"," const parsed = JSON.parse(raw) as { stream?: boolean };"," if (!parsed.stream) {",' return new Response(chatCompletionPayload("ok"), {'," status: 200,",' headers: { "content-type": "application/json" },'," });"," }"," const body = new ReadableStream({"," start(controller) {"," controller.enqueue("," encoder.encode("," `data: ${JSON.stringify({",' id: "stream-1",'," created: 1,",' model: "openrouter/test-model",',' object: "chat.completion.chunk",',' choices: [{ delta: { content: "hel" }, finish_reason: "stop", index: 0 }],'," })}\\n\\n`"," )"," );",' controller.enqueue(encoder.encode("data: [DONE]\\n\\n"));'," controller.close();"," },"," cancel() {",' throw new Error("cleanup-failed");'," },"," });",' return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });'," }),"," });"," const chunks: string[] = [];",' for await (const chunk of gateway.stream({ messages: [{ role: "user", content: "hi" }] })) {'," chunks.push(chunk);"," }",' assert.equal(chunks.join("").includes("hel"), true);',' const recovered = await gateway.generate({ messages: [{ role: "user", content: "hi" }] });',' assert.equal(recovered.text, "ok");'," await gateway.close();","});","",'test("already-aborted caller signal is rejected without hanging", { timeout: 5000 }, async () => {',' process.env.OPENROUTER_API_KEY = "sk-or-test";',' process.env.OPENROUTER_MODEL = "openrouter/test-model";'," const gateway = new OpenRouterModelGateway({"," config: loadGatewayConfig({ policy: {} }),"," httpClient: fakeClient(async (request) => {"," if (request.signal.aborted) {",' throw request.signal.reason ?? new Error("aborted");'," }"," const watchdog = setTimeout(() => undefined, 1000);"," await new Promise((_, reject) => {"," const fail = () => {"," clearTimeout(watchdog);",' reject(request.signal.reason ?? new Error("aborted"));'," };",' request.signal.addEventListener("abort", fail, { once: true });',' setTimeout(() => reject(new Error("AbortSignal was not forwarded to transport")), 1000);'," });",' return new Response("{}", { status: 200 });'," }),"," });"," const cancelled = new AbortController();"," cancelled.abort();"," await assert.rejects(() =>"," gateway.stream({",' messages: [{ role: "user", content: "hi" }],'," signal: cancelled.signal,"," }).next()"," );"," await gateway.close();","});"])}async function Ea(e){let t=zr(e.projectName);await e$1(x.join(e.projectPath,"package.json"),la(t)),await e$1(x.join(e.projectPath,"tsconfig.json"),ca()),await e$1(x.join(e.projectPath,de),da()),await e$1(x.join(e.projectPath,".env.example"),pa()),await e$1(x.join(e.projectPath,".gitignore"),st),await e$1(x.join(e.projectPath,"README.md"),ma(e.projectName)),await e$1(x.join(e.projectPath,"src","redact.ts"),ua()),await e$1(x.join(e.projectPath,"src","errors.ts"),fa()),await e$1(x.join(e.projectPath,"src","port.ts"),ga()),await e$1(x.join(e.projectPath,"src","config.ts"),_a()),await e$1(x.join(e.projectPath,"src","openrouter-gateway.ts"),ya()),await e$1(x.join(e.projectPath,"src","main.ts"),ha()),await e$1(x.join(e.projectPath,"src","index.ts"),wa()),await e$1(x.join(e.projectPath,"tests","gateway.test.ts"),ka());}var no={id:"openrouter-typescript",gatewayId:"openrouter",gatewayName:"OpenRouter",kitId:"gateway.openrouter.typescript",aliases:["gateway.openrouter.typescript","openrouter.typescript","gateway.openrouter.ts","openrouter-typescript"],label:"OpenRouter \xB7 TypeScript",runtime:"node",requiredEnvironment:["OPENROUTER_API_KEY","OPENROUTER_MODEL"],versionBaselineId:"openrouter-typescript",capabilities:pe,lifecycle:{stages:lt},attach:"unsupported",generate:Ea};function ao(e){return Object.freeze({id:e.id,gatewayId:e.gatewayId,gatewayName:e.gatewayName,kitId:e.kitId,aliases:Object.freeze([...e.aliases]),label:e.label,runtime:e.runtime,requiredEnvironment:Object.freeze([...e.requiredEnvironment]),versionBaselineId:e.versionBaselineId,capabilities:pe,lifecycle:Object.freeze({stages:Object.freeze([...e.lifecycle.stages])}),attach:"unsupported"})}function so(e){return {id:e.kitId,aliases:[...e.aliases],label:e.label,runtime:e.runtime,adapterId:e.id,gatewayId:e.gatewayId,gatewayName:e.gatewayName,requiredEnvironment:[...e.requiredEnvironment]}}function io(e){if(e.length===0)throw new Error("Model gateway registry requires at least one adapter.");let t=new Set,r=new Set,o=new Map;for(let n of e){if(!n.id||!n.kitId||!n.generate)throw new Error("Model gateway adapter is missing id, kitId, or generate.");if(t.has(n.id))throw new Error(`Duplicate model gateway adapter: ${n.id}`);if(r.has(n.kitId))throw new Error(`Duplicate model gateway kit: ${n.kitId}`);t.add(n.id),r.add(n.kitId);let l=[n.kitId,...n.aliases];for(let c of l){let i=c.trim().toLowerCase();if(!i)throw new Error(`Empty model gateway alias on ${n.id}.`);let p=o.get(i);if(p&&p!==n.id)throw new Error(`Duplicate model gateway alias: ${c}`);o.set(i,n.id);}}let a=Object.freeze([...e]);return {listAdapters(){return a.map(n=>ao(n))},listKits(){return a.map(n=>structuredClone(so(n)))},lookupKit:n=>{if(!n)return null;let l=n.trim().toLowerCase(),c=a.find(i=>i.kitId.toLowerCase()===l||i.aliases.some(p=>p.toLowerCase()===l));return c?structuredClone(so(c)):null},resolveAdapter(n){let l=a.find(c=>c.kitId===n.id);if(!l)throw new Error(`Unsupported model gateway kit: ${n.id}`);return l},async generate(n){let l=a.find(c=>c.kitId===n.kit.id);if(!l)throw new Error(`Unsupported model gateway kit: ${n.kit.id}`);await l.generate({projectPath:n.projectPath,projectName:n.projectName});}}}var Lt=io([no,ro]);function va(){return Lt.listKits()}function Jc(){return va()}function Aa(e){return Lt.lookupKit(e)}function Hc(e){return Aa(e)!==null}async function Zc(e){await Lt.generate({projectPath:e.projectPath,projectName:e.projectName,kit:e.kit}),await Qr(e);}var Ra=["minimal","python-only","node-only","go-only","java-only","dotnet-only","polyglot","enterprise"],ba=new Set(["minimal","node-only","go-only","java-only","dotnet-only"]),ld=new Set(["python-only","polyglot","enterprise"]);function Ta(e){return typeof e=="string"&&Ra.includes(e)}function cd(e){return Ta(e)&&ba.has(e)}var po={"python-only":"python","node-only":"node","go-only":"go","java-only":"java","dotnet-only":"dotnet"},Oa={python:"Python",node:"Node",go:"Go",java:"Java",dotnet:".NET",php:"PHP",ruby:"Ruby",rust:"Rust",elixir:"Elixir",clojure:"Clojure",scala:"Scala",kotlin:"Kotlin",deno:"Deno",bun:"Bun",c:"C",cpp:"C++",unknown:"Unknown"},Pa={python:"python-only",node:"node-only",go:"go-only",java:"java-only",dotnet:"dotnet-only"},lo=["node","deno","bun","python","go","java","kotlin","scala","dotnet","php","ruby","rust","elixir","clojure","c","cpp","unknown"];function qt(e){return e==="bun"||e==="deno"?"node":e==="kotlin"||e==="scala"?"java":e}function Wt(e){return typeof e!="string"||e.trim().length===0?"minimal":e.trim().toLowerCase()}function xe(e){if(typeof e!="string")return "unknown";let t=e.trim().toLowerCase();return t==="python"||t==="node"||t==="go"||t==="java"||t==="dotnet"||t==="php"||t==="ruby"||t==="rust"||t==="elixir"||t==="clojure"||t==="scala"||t==="kotlin"||t==="deno"||t==="bun"||t==="c"||t==="cpp"?t:t==="c++"||t==="cplusplus"||t==="cc"||t==="clang++"||t==="g++"?"cpp":t==="clang"||t==="gcc"?"c":"unknown"}function pt(e){let t=new Set;for(let r of e)t.add(xe(r));return [...t].sort((r,o)=>lo.indexOf(r)-lo.indexOf(o))}function Sa(e){let t=pt(e).filter(r=>r!=="unknown");if(t.length!==0)return t.length===1?Pa[t[0]]:"polyglot"}function co(e,t={}){return `npx workspai bootstrap --profile ${e}${t.ci?" --ci --json":""}`}function Na(e,t){return e?"none":t==="strict"?"error":"warning"}function Re(e){let t=e.ok?void 0:Sa(e.runtimes)??"polyglot";return {ok:e.ok,checkId:e.checkId,status:e.ok?"passed":"failed",profile:e.profile,runtimes:e.runtimes,message:e.message,recommendedProfile:t,recommendedCommand:t?co(t):void 0,ciRecommendedCommand:t?co(t,{ci:true}):void 0,severity:Na(e.ok,e.mode)}}function dd(e){let t=Wt(e.profile),r=pt(e.runtimes),o=e.mode??"warn",a=po[t];if(a){let s=r.length===0||r.every(n=>qt(n)===a);return Re({ok:s,checkId:`profile.${t}`,profile:t,runtimes:r,message:s?`${t} profile validated for discovered projects.`:`${t} profile mismatch: detected runtimes [${r.join(", ")}].`,mode:o})}if(t==="minimal"){let s=r.filter(c=>c!=="unknown"),l=new Set(s.map(c=>qt(c))).size<=1;return Re({ok:l,checkId:"profile.minimal",profile:t,runtimes:r,message:l?"minimal profile is compatible with detected runtime mix.":`minimal profile mismatch: multiple runtimes detected [${s.join(", ")}].`,mode:o})}return Re({ok:true,checkId:`profile.${t}`,profile:t,runtimes:r,message:`${t} profile accepts the detected runtime mix.`,mode:o})}function pd(e){let t=Wt(e.profile),r=xe(e.runtime),o=e.mode??"warn",a=po[t];if(!a||r==="unknown")return Re({ok:true,checkId:`profile.${t}`,profile:t,runtimes:[r],message:r==="unknown"?`Project "${e.subjectLabel}" runtime is unknown; profile compatibility will be checked by workspace doctor.`:`${t} profile accepts project "${e.subjectLabel}".`,mode:o});let s=qt(r)===a;return Re({ok:s,checkId:`profile.${t}`,profile:t,runtimes:s?[r]:pt([a,r]),message:s?`Project "${e.subjectLabel}" is compatible with workspace profile "${t}".`:`Project "${e.subjectLabel}" is ${Oa[r]}, but workspace profile is "${t}".`,mode:o})}async function md(e){let t$1=(await Promise.all(t(e,"policies.yml").map(async r=>await M.pathExists(r)?r:null))).find(r=>typeof r=="string");if(!t$1)return "warn";try{return (await M.readFile(t$1,"utf-8")).match(/^\s*mode:\s*(warn|strict)\s*(?:#.*)?$/m)?.[1]==="strict"?"strict":"warn"}catch{return "warn"}}async function ud(e){let t$1=(await Promise.all(t(e,"workspace.json").map(async r=>await M.pathExists(r)?r:null))).find(r=>typeof r=="string");if(!t$1)return "minimal";try{let r=await M.readJson(t$1);return Wt(r.profile)}catch{return "minimal"}}async function fd(e,t={}){let r=new Set,o=[],a=new Set,s=[e];for(;s.length>0;){let n=s.pop();if(!n||a.has(n))continue;a.add(n);let l=[];try{l=await xa.promises.readdir(n,{withFileTypes:true});}catch{continue}for(let c of l){if(!c.isDirectory()||c.name.startsWith(".")||["node_modules","dist","build","target","coverage","htmlcov"].includes(c.name))continue;let i=x.join(n,c.name);if(i!==e&&k$1(i))continue;if((await Promise.all([...u(i,"context.json"),...u(i,"project.json")].map(async _=>M.pathExists(_)))).some(Boolean)){o.push(i);continue}s.push(i);}}for(let n of o){if(t.excludeProjectPath&&x.resolve(n)===x.resolve(t.excludeProjectPath))continue;let l=j(n);r.add(xe(m(n,l).runtime));}for(let n of await a$1(e))t.excludeProjectPath&&x.resolve(n.path)===x.resolve(t.excludeProjectPath)||r.add(xe(n.runtime));for(let n of t.additionalRuntimes??[])r.add(xe(n));return pt(r)}function gd(e,t={}){let r=t.ci?e.ciRecommendedCommand:e.recommendedCommand;return r?`Recommended: ${r}`:null}
4746
+ export{vs as A,As as B,xs as C,b as D,jt as E,Vr as F,qn as G,Xl as H,Jl as I,Br as J,Hl as K,Zl as L,Vn as M,rt as N,va as O,Jc as P,Aa as Q,Hc as R,Zc as S,ld as T,cd as U,dd as V,pd as W,md as X,ud as Y,fd as Z,gd as _,N as a,Yt as b,Xt as c,Jt as d,Ht as e,Zt as f,Qt as g,er as h,tr as i,Pe as j,Za as k,fe as l,Qa as m,es as n,wo as o,ts as p,fs as q,yt as r,gs as s,Oo as t,_s as u,ws as v,U as w,Do as x,ks as y,Es as z};