{"_id":"@cana-ai/sdk","_rev":"15-1f84a57da07b444366379898656e7df2","name":"@cana-ai/sdk","dist-tags":{"latest":"2.1.1"},"versions":{"0.2.0":{"name":"@cana-ai/sdk","version":"0.2.0","_id":"@cana-ai/sdk@0.2.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"}],"dist":{"shasum":"8956ab8949a57db889383c2c9f5cd1f197c6193e","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.2.0.tgz","fileCount":25,"integrity":"sha512-LUXSNex0vGXKYUZFgHIEWhNMuGkiVsU2vBjcm6Tw1ZpKoo0yO/9x1OdjB9m7lsygUQ8Mx7pOTKxqXQy6IL0q/A==","signatures":[{"sig":"MEUCIFXdoSW7TMBtvMdTJfhcqQs/ef38vyz3whtTLsWTnZauAiEAmhJifFUhWNuwVPDV7ymY8XBBwIgLCEyx+wiQdp+yNPE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":64929},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"26cdb3b120470779ddc7049b7540bfe66f71f3d6","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"Cana Apps SDK — programmatic access for embedders who drive their own UI.","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.0_1781466587508_0.011179811442806553","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@cana-ai/sdk","version":"0.2.1","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@0.2.1","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"1167931822134183e538e20e46f6abac00f17ab0","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.2.1.tgz","fileCount":26,"integrity":"sha512-j5nsW7sH9Z61daA4V0kj94NMXFzXGjlAahgwTH4VXX+wRfw0kc6FzaCpl0Yh0bx+DoisRn1cflVgCQ7TtVuoBw==","signatures":[{"sig":"MEUCIQCdEgwpodnoVWiR9VKZG9wPXQoTdYJn4KAql/PyiGObUAIgamL1c6yeJ8InRHK35znDQvB87bMCZX2hDf7GfC5dyJ4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":69950},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"26cdb3b120470779ddc7049b7540bfe66f71f3d6","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client for Cana Apps — HMAC auth, server-hosted MCP federation, streaming, typed structured output. Web Crypto only (Node 18+, Bun, Deno, Workers, browsers).","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.1_1781466760718_0.3854694678191495","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@cana-ai/sdk","version":"0.2.2","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@0.2.2","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"e5c22ca56351332dc1cf41f6ce3665c895e39f6a","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.2.2.tgz","fileCount":30,"integrity":"sha512-W4pePctbfTcqF9/9ShIJSE/lgPOW6zkESl1HvCunqrDySLO1nVyI88FSCYVCzNtRQnmGf64XEKElZdh4vpy/tg==","signatures":[{"sig":"MEUCIQDsZxvyQX3IedeXAYs0j6WemNlizoOAly5DvILIuVytbAIgXjsDhsfF3QWzDFqv3dHTwT2Ng/aUNKTsewIOGT/WKyo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":146566},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"fcd76acf0cce18d6f763d8047eaae54383c2c1bd","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client for Cana Apps — HMAC auth, server-hosted MCP federation, streaming, typed structured output. Web Crypto only (Node 18+, Bun, Deno, Workers, browsers).","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.2_1781467041525_0.6985202480986474","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@cana-ai/sdk","version":"0.2.3","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@0.2.3","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"0ecaf716f472a8f2cb2bd2984648d47445098efe","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.2.3.tgz","fileCount":30,"integrity":"sha512-Sa+7z/EIyQfO2oSHoy4iqKBqGaETepwxBb7knK1JckZSaWWSqgfe9YFXZvSKScKbdi00/zjuLyoc9H4c34L0TA==","signatures":[{"sig":"MEUCIQDMQyUOp1uPQ2BO/dFO6IpnQ+ZvrBfnIkM8iUEWLvK/FwIgGSwCXOMla0JJh6zXRp2l1j7Dan3kGRAfkZJBpcRl2tM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":160338},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"7c1e71aaac8dd2a46c25f2e6db3d6c990f48ceb3","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client for Cana Apps — HMAC auth, server-hosted MCP federation, streaming, typed structured output. Web Crypto only (Node 18+, Bun, Deno, Workers, browsers).","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.2.3_1781472440378_0.3490105371400607","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@cana-ai/sdk","version":"0.3.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@0.3.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"f0bfd86d62779ba2fa6ebe0e294217196b0dfbe7","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.3.0.tgz","fileCount":34,"integrity":"sha512-0uKwQx5RXnLdDcMnJKj/iZ88U8UxAsLmAnddncks9052aLOURjyLuecb5WnWtF0j/QxGqZ6Mi5XyGnSkyP2e1A==","signatures":[{"sig":"MEUCIA133/2yxsyDKNSn24VItpAhi5j/6ajWzjXOQw0UINtrAiEAkVKO3pB5D2LMkBvZnmlHZOrjStaZpu+MTczfLyvsBMk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":296806},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"5925373de8976406c1899b33c020af4740174b64","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client for Cana Apps — HMAC auth, server-hosted MCP federation, streaming, typed structured output. Web Crypto only (Node 18+, Bun, Deno, Workers, browsers).","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.3.0_1781474125306_0.3809876455171479","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"name":"@cana-ai/sdk","version":"0.3.1","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@0.3.1","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"6ccbfcc6c24362352a08fbe068920af20ac64e9b","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.3.1.tgz","fileCount":34,"integrity":"sha512-mOEytip31fCFhPPXwdbG7adjXBthry4nPuklwECb3k987n4sLUFCnhl8wQOCng+9KJLWiddODh/T0CPXuhVscA==","signatures":[{"sig":"MEQCIA3Y8lb/23HZtty+oB0QCT4EJwS+yk5P187WP0TRLmZ3AiB+cVYNwySGYQrOjDCeHjL8UnnmvMKjs61sPaXy1VS2VA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":301610},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"f556084d382c8feba3f2813ea87e2d5018c9aa37","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client for Cana Apps — HMAC auth, server-hosted MCP federation, streaming, typed structured output. Web Crypto only (Node 18+, Bun, Deno, Workers, browsers).","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.3.1_1781511275625_0.8993006329963793","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@cana-ai/sdk","version":"0.4.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@0.4.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"92e43237b361eaff37a3208cdf364978ea4fcd75","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-0.4.0.tgz","fileCount":38,"integrity":"sha512-TYH29qU60FjU/YkSYUcrkcTzY7acqALhMKMyxUXddcSrvOCXJhXjH1lyenptBejDjeXsZvABHFCTdBAHUyDpPw==","signatures":[{"sig":"MEQCIBaA8oLMiw+Tc9t4Ik0qPOZ4Zg745mmcxWdI0zkt9K50AiAGilK2SyBht6O1ZeW6QF5VYhNe5bkpoBsWi9vKGEns/g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":364776},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"87c441f3f1f4fbf60ee3ae04723db59279548d1d","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client for Cana Apps — HMAC auth, server-hosted MCP federation, streaming, typed structured output. Web Crypto only (Node 18+, Bun, Deno, Workers, browsers).","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.4.0_1781516316050_0.22098877504286496","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@cana-ai/sdk","version":"1.0.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@1.0.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"22b2a6769a2a4167305644e53c1df6d7c6a80a5a","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-1.0.0.tgz","fileCount":38,"integrity":"sha512-lx7xycg0NmXmPiJlKtKfdfaq4URSyLsHWwbUhWR32HveogkuKwBCpBSJ6Pba1YFTNYEhnwYf0vjgisceWf0g9Q==","signatures":[{"sig":"MEQCICFlSmiDV9ozbxjwEoFHWL6UoRCg9wSCE8hLrjVx5O47AiBiYm+4Ky4k9eS0fPFL0S1NFT70Dzj8yjrivzi9NKOdaA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":392813},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"f1c59fdcb064f190e2e55735c02efb4f764294b2","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.0_1781523063880_0.8145977205009085","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@cana-ai/sdk","version":"1.0.1","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@1.0.1","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"9a7e1678b11d0b22574d8d66093316b09ae3bcc2","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-1.0.1.tgz","fileCount":38,"integrity":"sha512-FkFz0c4xKbCm2BpVwGOy0RaFP1ZP7tIrX9FFM6z/0fPFjBV3UiHUnDsJWZH3ZJr//hWVq+fNUCObcvaC4DRPuQ==","signatures":[{"sig":"MEUCIEsbivBY/jktrq8T37U43e4f6K0vxWFIh6XUz20DrCIdAiEAg/Yvk/mYBcYmpq2XzsBSgK7j+FQYWue7MhexJZZ0SGM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":394183},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"4d19de5388272a8972374c00fb8e99909d839d12","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"himansh.raj","email":"himansh.raj@colate.io"},"_npmVersion":"11.13.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"_nodeVersion":"24.16.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.24.0","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.1_1781523913001_0.9746965035389097","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@cana-ai/sdk","version":"1.1.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@1.1.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"f168e211a2a5589289cd9f711f8610af93630ba9","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-1.1.0.tgz","fileCount":38,"integrity":"sha512-RBTQiUqdq4JuKwFfOeNZA4AdWKEozokDCXBsUGqw345QhdYwLOi8MikwAKc7KX0NxH71CjPLUCl0bN0Tt1cxFg==","signatures":[{"sig":"MEUCIQCxyvhBUgyd5aAutkoialJhksCCu8bSpHlGjItnXPFHugIgdxWS7F8xpkdOoFNiJDMV/eTlfVVWMExreqmZy86OoCM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":435239},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./openai":{"types":"./dist/openai/index.d.ts","import":"./dist/openai/index.js"}},"gitHead":"d6a5a7f48cdd9da4dea4b9d2e053f4359e2643ae","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"prasadpanda","email":"prasad.p@colate.io"},"_npmVersion":"11.17.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.25.12","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.1.0_1788829201168_0.4327529226500979","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@cana-ai/sdk","version":"1.2.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@1.2.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"18890a98e5c3d09210c5ffe4e1c71496ef70c614","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-1.2.0.tgz","fileCount":71,"integrity":"sha512-Jom04ubrowupx01GCDU6J5v4KovJzK4zsaB55Er4zjV208Wx3oT74QzQZ7IfuWYNlcK8Pv+Yz5CKY2SBoOBHVQ==","signatures":[{"sig":"MEUCIDxSAzaaxvOJ423riACTznnVFxlrqXae3jENtbiUFPLzAiEAitS2HreqQxIzqF2em88Oka3FVWMOJfmHROqGDpLTbd4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":636505},"main":"./dist/cjs/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"default":"./dist/index.js","require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./openai":{"import":{"types":"./dist/openai/index.d.ts","default":"./dist/openai/index.js"},"default":"./dist/openai/index.js","require":{"types":"./dist/cjs/openai/index.d.ts","default":"./dist/cjs/openai/index.js"}}},"gitHead":"004536432e95dd852ea9552b904f239952614468","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-cjs.mjs && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"prasadpanda","email":"prasad.p@colate.io"},"_npmVersion":"11.17.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.25.12","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.2.0_1788869942598_0.6333582799411892","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@cana-ai/sdk","version":"2.0.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@2.0.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"08080f9328048d03d224f4762279d6bc29630da6","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-2.0.0.tgz","fileCount":111,"integrity":"sha512-mtgIMQYcJfRPD08R2GPf7syrhbXow1eWMmePL1e5RpmyV8/nifIYM7f9+9N/rKR9BoUVdvNQFNYnrE9iAtySxQ==","signatures":[{"sig":"MEUCIQDT6Bs4Du7YlsaSbjnKorcNeTupAaWmkSvFFP0Z9ZU/fgIgTYKuQx9BGcvas6I1F8BvdvPwWCLAx6ESDoctzwllLF4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQC5Cva1/unc4PxleoMFiVo2HSbCWrnQFLScHCLppkHMgAIhAMbeueg1wO0gamdU965T4L2rLLPfPOMWT9E+MilQTPT3","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":768187},"main":"./dist/cjs/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"default":"./dist/index.js","require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./agent":{"import":{"types":"./dist/agent/index.d.ts","default":"./dist/agent/index.js"},"default":"./dist/agent/index.js","require":{"types":"./dist/cjs/agent/index.d.ts","default":"./dist/cjs/agent/index.js"}},"./openai":{"import":{"types":"./dist/openai/index.d.ts","default":"./dist/openai/index.js"},"default":"./dist/openai/index.js","require":{"types":"./dist/cjs/openai/index.d.ts","default":"./dist/cjs/openai/index.js"}}},"gitHead":"5b274804e4dcb571349436e31c8db3562b43cc2a","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-cjs.mjs && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build"},"_npmUser":{"name":"prasadpanda","email":"prasad.p@colate.io"},"_npmVersion":"11.17.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","vitest":"^3.0.0","esbuild":"^0.25.12","typescript":"^5.8.3","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_2.0.0_1789075372938_0.508224962149284","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@cana-ai/sdk","version":"2.1.0","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"license":"MIT","_id":"@cana-ai/sdk@2.1.0","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"homepage":"https://cana.build","dist":{"shasum":"037275f84b36d51cff5a0f5910ee980a12604d27","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-2.1.0.tgz","fileCount":139,"integrity":"sha512-RFcrw1yY0MiUb/dYw5MY4kUp6WPRYAsmAXrlTfjdGxw2F7ghEDI99aYNUBmxwsNHSuQZmS3s5mxhcdycfTBioQ==","signatures":[{"sig":"MEUCIQCoSo7WtS7V+ehi/G4kKFGP004xuYmIeLwYpeoCDUcTfgIgNclIHi0W46M++qDEotJWCzQ0Zxr6wWcKgeEJnN+YAWI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIHsmpzzUxfiw/yORSsoZUbQMfAxCe4exIPAShCurXCaCAiEAvuCu4SHF8qUD7nqdBa0PxJCLSH7JAZLZPG0GJ0IFFgE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":928511},"main":"./dist/cjs/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"default":"./dist/index.js","require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./ui":{"import":{"types":"./dist/ui/index.d.ts","default":"./dist/ui/index.js"},"default":"./dist/ui/index.js","require":{"types":"./dist/cjs/ui/index.d.ts","default":"./dist/cjs/ui/index.js"}},"./agent":{"import":{"types":"./dist/agent/index.d.ts","default":"./dist/agent/index.js"},"default":"./dist/agent/index.js","require":{"types":"./dist/cjs/agent/index.d.ts","default":"./dist/cjs/agent/index.js"}},"./openai":{"import":{"types":"./dist/openai/index.d.ts","default":"./dist/openai/index.js"},"default":"./dist/openai/index.js","require":{"types":"./dist/cjs/openai/index.d.ts","default":"./dist/cjs/openai/index.js"}},"./ui/styles.css":{"import":{"types":"./dist/ui/styles.css.d.ts","default":"./dist/ui/styles.css"},"default":"./dist/ui/styles.css","require":{"types":"./dist/cjs/ui/styles.css.d.ts","default":"./dist/cjs/ui/styles.css"}}},"scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-cjs.mjs && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build","verify:ui-package":"node scripts/verify-ui-package.mjs"},"_npmUser":{"name":"prasadpanda","email":"prasad.p@colate.io"},"_npmVersion":"11.17.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","react":"^19.0.0","vitest":"^3.0.0","esbuild":"^0.25.12","react-dom":"^19.0.0","typescript":"^5.8.3","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"react":">=18.0.0","zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/sdk_2.1.0_1789113731199_0.13928314567345468","host":"s3://npm-registry-packages-npm-production"}},"2.1.1":{"_id":"@cana-ai/sdk@2.1.1","dist":{"shasum":"eb66d4ece244095655682a3547c0c4aaf4d51c03","tarball":"https://registry.npmjs.org/@cana-ai/sdk/-/sdk-2.1.1.tgz","fileCount":139,"integrity":"sha512-lQ99Dab9N4yqurGSo5X1D0oEsxGpS6hx/fBERP1iqMFzo6U8RaOKhfLMc4NGgaiF4SNAtbZ2rI7PZxXGpzTZKA==","signatures":[{"sig":"MEQCIECCrfcrlMIgPyyqAhLD7drJPp62odMirEWbXAQfp/8bAiAnG72+YljJX2K4Phfb3NRPXXfUGBz4xmj8aBgPvMMBOg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC5ObBS/e1AgqojI5d1lj4J+fNdqjbPTmb7GUkW+jDHUQIhALzt9zBmNPPHugYp8SoQaqaXqhjVWpedtEkreg1SnA7m"}],"unpackedSize":938199},"main":"./dist/cjs/index.js","name":"@cana-ai/sdk","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"default":"./dist/index.js","require":{"types":"./dist/cjs/index.d.ts","default":"./dist/cjs/index.js"}},"./ui":{"import":{"types":"./dist/ui/index.d.ts","default":"./dist/ui/index.js"},"default":"./dist/ui/index.js","require":{"types":"./dist/cjs/ui/index.d.ts","default":"./dist/cjs/ui/index.js"}},"./agent":{"import":{"types":"./dist/agent/index.d.ts","default":"./dist/agent/index.js"},"default":"./dist/agent/index.js","require":{"types":"./dist/cjs/agent/index.d.ts","default":"./dist/cjs/agent/index.js"}},"./openai":{"import":{"types":"./dist/openai/index.d.ts","default":"./dist/openai/index.js"},"default":"./dist/openai/index.js","require":{"types":"./dist/cjs/openai/index.d.ts","default":"./dist/cjs/openai/index.js"}},"./ui/styles.css":{"import":{"types":"./dist/ui/styles.css.d.ts","default":"./dist/ui/styles.css"},"default":"./dist/ui/styles.css","require":{"types":"./dist/cjs/ui/styles.css.d.ts","default":"./dist/cjs/ui/styles.css"}}},"license":"MIT","scripts":{"dev":"tsc -p tsconfig.json --watch","test":"vitest run","build":"rm -rf dist && tsc -p tsconfig.build.json && node scripts/build-cjs.mjs && node scripts/build-iife.mjs","build:iife":"node scripts/build-iife.mjs","test:watch":"vitest","prepublishOnly":"pnpm run build","verify:ui-package":"node scripts/verify-ui-package.mjs","verify:agent-boundary":"node scripts/verify-agent-boundary.mjs"},"version":"2.1.1","_npmUser":{"name":"prasadpanda","email":"prasad.p@colate.io"},"homepage":"https://cana.build","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"_npmVersion":"11.17.0","description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","directories":{},"maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"_nodeVersion":"26.5.0","_hasShrinkwrap":false,"devDependencies":{"zod":"^3.24.1","react":"^19.0.0","vitest":"^3.0.0","esbuild":"^0.25.12","react-dom":"^19.0.0","typescript":"^5.8.3","@types/react":"^19.0.0","@types/react-dom":"^19.0.0","zod-to-json-schema":"^3.24.0"},"peerDependencies":{"react":">=18.0.0","zod-to-json-schema":"^3.0.0"},"peerDependenciesMeta":{"react":{"optional":true},"zod-to-json-schema":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_2.1.1_1789162760970_0.5439989838013441"}}},"time":{"created":"2026-06-14T19:49:47.360Z","modified":"2026-09-11T21:39:21.267Z","0.2.0":"2026-06-14T19:49:47.668Z","0.2.1":"2026-06-14T19:52:40.852Z","0.2.2":"2026-06-14T19:57:21.657Z","0.2.3":"2026-06-14T21:27:20.526Z","0.3.0":"2026-06-14T21:55:25.441Z","0.3.1":"2026-06-15T08:14:35.754Z","0.4.0":"2026-06-15T09:38:36.189Z","1.0.0":"2026-06-15T11:31:04.039Z","1.0.1":"2026-06-15T11:45:13.164Z","1.1.0":"2026-09-08T01:00:01.312Z","1.2.0":"2026-09-08T12:19:02.752Z","2.0.0":"2026-09-10T21:22:53.064Z","2.1.0":"2026-09-11T08:02:11.295Z","2.1.1":"2026-09-11T21:39:21.110Z"},"license":"MIT","homepage":"https://cana.build","keywords":["cana","openai","openai-compatible","llm","agent","mcp","model-context-protocol","structured-output","hmac","ai-sdk","anthropic","moonshot"],"description":"OpenAI-compatible client + agent for Cana Apps — Bearer auth (App API key or short-lived browser JWT with auto-refresh), server-hosted MCP federation, streaming, typed structured output. Node 18+, Bun, Deno, Workers, browsers.","maintainers":[{"name":"himansh.raj","email":"himansh.raj@colate.io"},{"name":"prasadpanda","email":"prasad.p@colate.io"}],"readme":"# @cana-ai/sdk\n\nOpenAI-compatible client + **prebuilt agent** for **Cana Apps** — Bearer auth\n(App API key or short-lived browser JWT with auto-refresh), server-hosted MCP\nfederation, streaming, typed structured output, and a hybrid local-tool loop. Web Crypto only — runs on **Node 18+, Bun, Deno,\nCloudflare Workers, and modern browsers** out of the box.\n\n```bash\nnpm install @cana-ai/sdk\n# or pnpm add / yarn add / bun add\n```\n\n```html\n<!-- or drop the IIFE bundle into a page -->\n<script src=\"https://cana.build/sdk/openai.js\"></script>\n```\n\n---\n\n## Table of contents\n\n- [Quick start (prebuilt agent)](#quick-start-prebuilt-agent)\n- [Generated UI renderer](#generated-ui-renderer)\n- [Agent mode: server-to-server transport](#agent-mode-server-to-server-transport)\n- [Authentication](#authentication)\n- [Model strings](#model-strings)\n- [Prebuilt agent: `CanaAgent`](#prebuilt-agent-canaagent)\n  - [`agent.run` — one-shot](#agentrun--one-shot)\n  - [`defineTool` — Zod + JSON Schema](#definetool--zod--json-schema)\n  - [Hybrid local + MCP tools](#hybrid-local--mcp-tools)\n  - [`agent.stream` — streamed events](#agentstream--streamed-events)\n  - [`AgentSession` — multi-turn memory](#agentsession--multi-turn-memory)\n  - [Handler errors are captured, not thrown](#handler-errors-are-captured-not-thrown)\n- [Low-level client: `CanaClient`](#low-level-client-canaclient)\n  - [Chat completions](#chat-completions)\n  - [Streaming](#streaming)\n  - [Function tools — manual dispatch loop](#function-tools--manual-dispatch-loop)\n  - [MCP federation — `runWithMcp` auto-dispatch](#mcp-federation--runwithmcp-auto-dispatch)\n  - [MCP federation — manual](#mcp-federation--manual)\n- [Structured output (JSON Schema)](#structured-output-json-schema)\n- [Structured output (Zod helper)](#structured-output-zod-helper)\n- [System prompts & multi-turn](#system-prompts--multi-turn)\n- [Usage / cost tracking](#usage--cost-tracking)\n- [Custom `fetch` (testing, proxies, telemetry)](#custom-fetch-testing-proxies-telemetry)\n- [Hosted page / embed: `CanaIssuer`](#hosted-page--embed-canaissuer)\n- [Browser — `<script>` tag](#browser--script-tag)\n- [Plain OpenAI SDK against the proxy](#plain-openai-sdk-against-the-proxy)\n- [Low-level: `signRequest`](#low-level-signrequest)\n- [Low-level: `parseChunkStream`](#low-level-parsechunkstream)\n- [Reliability: retries, timeouts, backoff](#reliability-retries-timeouts-backoff)\n- [Errors](#errors)\n- [TypeScript exports](#typescript-exports)\n\n---\n\n## Quick start (prebuilt agent)\n\nPlug in a prompt + tools. The SDK handles auth, MCP discovery, tool\ndispatch, the chat loop, streaming, and error capture.\n\n```ts\nimport { CanaAgent, defineTool } from \"@cana-ai/sdk/openai\";\nimport { z } from \"zod\";\n\nconst get_weather = defineTool({\n  description: \"Current weather for a city\",\n  parameters:  z.object({\n    city:  z.string(),\n    units: z.enum([\"c\", \"f\"]).optional(),\n  }),\n  handler: async ({ city, units }) => {\n    //                ^^^^^^^^^^^^\n    //                typed string + \"c\"|\"f\"|undefined via z.infer\n    return await fetchWeather(city, units);\n  },\n});\n\nconst agent = new CanaAgent({\n  appId:        \"app_…\",\n  apiKey:       process.env.CANA_APP_KEY!,   // cak_live_…\n  // or for the browser: getToken: () => fetchFreshJwtFromYourBackend()\n  model:        \"OpenAI/gpt-4o-mini\",\n  systemPrompt: \"You are helpful.\",\n  tools:        { get_weather },     // local — handler runs in your process\n  mcp:          [\"github\"],          // server-hosted — auto-dispatched\n});\n\nconst r = await agent.run({ prompt: \"What's the weather in Tokyo?\" });\nconsole.log(r.text);          // \"It's 22°C and sunny in Tokyo.\"\nconsole.log(r.toolCalls);     // [{ name: \"get_weather\", args: { city: \"Tokyo\" }, result: { … }, source: \"local\" }]\nconsole.log(r.usage);         // { prompt_tokens, completion_tokens, total_tokens }\n```\n\nThat's it — no chat loop to write, no `messages` array to manage, no\n`tool_calls` to dispatch yourself.\n\nFor full control (driving rounds yourself, custom tool dispatch logic, raw\nstreaming chunks), drop down to the [`CanaClient`](#low-level-client-canaclient)\ndocumented below.\n\n## Generated UI renderer\n\n`@cana-ai/sdk/ui` renders UI-catalog node trees as React components. It never\ninjects model-authored HTML. Text stays text, charts are inline SVG, and unknown\nnode kinds produce a visible fallback instead of disappearing.\n\nReact 18 or newer is required only when you import this subpath. The SDK root,\n`/openai`, and `/agent` remain framework independent.\n\n```tsx\nimport { CanaUiRenderer, type UiNode } from \"@cana-ai/sdk/ui\";\nimport \"@cana-ai/sdk/ui/styles.css\";\n\nconst node: UiNode = {\n  kind: \"dashboard\",\n  children: [\n    {\n      kind: \"metricRow\",\n      children: [\n        {\n          kind: \"metric\",\n          props: {\n            label: \"Monthly spend\",\n            value: \"$38,080\",\n            delta: \"-12%\",\n            // Direction is semantic. A falling cost is good.\n            direction: \"up\",\n          },\n        },\n      ],\n    },\n    {\n      kind: \"chart\",\n      props: {\n        chartType: \"line\",\n        labels: [\"Mon\", \"Tue\", \"Wed\"],\n        series: [{ name: \"Bookings\", data: [8, 13, 10] }],\n      },\n    },\n  ],\n};\n\nexport function Dashboard() {\n  return <CanaUiRenderer root={node} />;\n}\n```\n\nThe stylesheet has a neutral light and dark theme. Dark mode follows a `.dark`,\n`[data-theme=\"dark\"]`, or `[data-cana-ui-theme=\"dark\"]` ancestor.\n\n### Customize the theme\n\nPass typed semantic tokens to one renderer:\n\n```tsx\n<CanaUiRenderer\n  root={node}\n  theme={{\n    primary: \"#0d9488\",\n    primarySubtle: \"#ccfbf1\",\n    radiusCard: \"8px\",\n    shadow: \"none\",\n    series: [\"#0d9488\", \"#2563eb\", \"#d97706\"],\n  }}\n/>\n```\n\nOr set the same public CSS variables on an ancestor. This is useful when the\nproduct already has design tokens:\n\n```css\n.product-dashboard {\n  --cana-ui-foreground: var(--foreground);\n  --cana-ui-muted-foreground: var(--muted-foreground);\n  --cana-ui-surface: var(--card);\n  --cana-ui-border: var(--border);\n  --cana-ui-primary: var(--brand);\n  --cana-ui-radius-card: var(--radius-lg);\n}\n```\n\n```tsx\n<div className=\"product-dashboard\">\n  <CanaUiRenderer root={node} />\n</div>\n```\n\n### Replace components\n\nUse `components` when a product needs more than token changes. An override\nreceives the node, the current class names, and a `renderChildren` function.\nOther node kinds keep the SDK implementation.\n\n```tsx\nimport type { CanaUiComponentProps } from \"@cana-ai/sdk/ui\";\n\nfunction ProductMetric({ node }: CanaUiComponentProps) {\n  return (\n    <article className=\"product-metric\">\n      <small>{String(node.props?.label ?? \"\")}</small>\n      <strong>{String(node.props?.value ?? \"\")}</strong>\n    </article>\n  );\n}\n\n<CanaUiRenderer root={node} components={{ metric: ProductMetric }} />;\n```\n\n`classNames` can replace individual SDK class names when a product already has\nmatching primitives. The default stylesheet is optional in that case.\n\n## Agent mode: server-to-server transport\n\n`@cana-ai/sdk/agent` is the thin client for Cana's hosted Agent runtime. It\nnormalizes the HTTP resources, reads the durable SSE log, dispatches customer\nimplementations for App-registered tools, and submits their results. Prompt\nconstruction, context management, compaction, evidence gates, recovery,\ncancellation policy, approvals, redaction, retention, and hosted-tool execution\nremain authoritative on Cana's server.\n\nAgent routes accept an App API key and are **server-to-server only**. Never put a\n`cak_live_…` key in a browser bundle. Browser JWT options supported by\n`CanaClient`/`CanaAgent` do not apply to `CanaAgentClient`.\n\n```ts\nimport {\n  CanaAgentClient,\n  ToolsApi,\n  runOnce,\n} from \"@cana-ai/sdk/agent\";\n\nconst client = new CanaAgentClient({\n  appId: \"app_abc\",\n  apiKey: process.env.CANA_API_KEY!,\n});\n\n// Tool schemas and permissions belong to the App-wide server registry.\nawait new ToolsApi(client).replace([\n  {\n    name: \"list_environments\",\n    description: \"List environments the signed-in customer may inspect\",\n    parameters: { type: \"object\", properties: {}, additionalProperties: false },\n    permission: \"AUTO_RUN\",\n    mutating: false,\n  },\n]);\n\nconst { run } = await runOnce(client, {\n  userKey: \"customer-42\",\n  prompt: \"Which environments are idle?\",\n  tools: {\n    list_environments: async () => listAuthorizedEnvironments(\"customer-42\"),\n  },\n  onEvent: (event) => {\n    if (event.type === \"message.delta\") process.stdout.write(String(event.data));\n  },\n});\n\nconsole.log(run.status);\n```\n\nRun creation accepts `prompt`, plus optional `model` and completion `contract`.\nIt does not accept per-run tool declarations: use `ToolsApi.replace` to configure\nthe App registry and pass only the customer-side dispatch functions to\n`runAgentic`/`runOnce`.\n\nA tool registered with `permission: \"CONFIRM\"` fails closed unless the caller\nsupplies `confirm` and returns `true` after obtaining its own explicit approval.\nThe SDK never invents an approval policy. The event stream stops at\n`run.requires_action`; the helper submits tool results and reconnects from the\nlast sequence cursor, suppressing replayed sequence IDs.\n\n## Authentication\n\n> **⚠ Breaking change in v1.0.0.** `CanaClient` and `CanaAgent` no longer use\n> HMAC signing, and the `signingKey` constructor option has been removed.\n> Callers on `signingKey` must switch to `apiKey` (or `getToken`). The\n> [`CanaIssuer`](#hosted-page--embed-canaissuer) flow is unchanged.\n\n`CanaAgent` and `CanaClient` authenticate with a **Bearer credential**. Pass\n**exactly one** of three options:\n\n- **`apiKey: \"cak_live_…\"`** — a static App API key for **server-to-server**\n  use. Sent as `Authorization: Bearer cak_live_…`. Create and revoke it from\n  the App's **OpenAI API** dashboard tab; it's hash-stored and shown only\n  once at creation, so copy it then. Never ship it in browser code.\n\n- **`getToken: () => Promise<string> | string`** — for **direct-browser**\n  use. Returns a short-lived embedder JWT. The client caches it, refreshes\n  ~5s before the JWT's `exp`, and on a `401` re-calls `getToken` **once** and\n  retries. This is the **safe browser path** — no long-lived secret ever\n  reaches page source.\n\n- **`bearerToken: \"<jwt>\"`** — a static JWT you manage yourself (a degenerate\n  `getToken`: not refreshed; a `401` propagates).\n\n```ts\n// server-to-server\nnew CanaAgent({ appId, apiKey: process.env.CANA_APP_KEY! });\n\n// browser — short-lived JWT, auto-refreshed on exp/401\nnew CanaAgent({ appId, getToken: () => fetch(\"/my-backend/cana-jwt\").then(r => r.text()) });\n\n// self-managed static JWT\nnew CanaAgent({ appId, bearerToken: myJwt });\n```\n\nThe JWTs returned by `getToken` / passed as `bearerToken` are minted by your\nserver with [`CanaIssuer`](#hosted-page--embed-canaissuer). Use `apiKey` for\nserver-to-server, `getToken` for the browser, and `bearerToken` only when you\nalready have a JWT and want to manage its lifecycle yourself.\n\n## Model strings\n\nModels are written as `<providerConfigName>/<modelId>`. `providerConfigName`\nis the *exact name* of an `LlmProviderConfig` the App owner has access to\n(case-insensitive; whitespace-stripped at create time). The proxy:\n\n1. finds an active config of that name (ORG > PLATFORM scope; newer wins\n   on ties),\n2. confirms it hosts the requested `modelId`,\n3. forwards your request with the config's `baseUrl` + decrypted apiKey.\n\n```ts\n// Examples — substitute YOUR provider config names\n\"Kimi/moonshot-v1-32k\"\n\"OpenAI/gpt-4o-mini\"\n\"Anthropic/claude-haiku-4-5-20251001\"\n\"Vertex/gemini-2.5-pro\"\n\n// No slash → fallback: any accessible config hosting that model id wins.\n\"gpt-4o-mini\"\n```\n\nTwo distinct errors disambiguate misses: `unknown_provider` (no config of\nthat name) vs `model_not_found` (config exists but doesn't host the model).\n\n---\n\n## Prebuilt agent: `CanaAgent`\n\n### `agent.run` — one-shot\n\n```ts\nconst r = await agent.run({\n  prompt: \"...\",\n  // or full history:\n  // messages: [...],\n  // overrides per-call:\n  // mcp: [\"slack\"], maxRounds: 5, excludeTools: [\"risky_tool\"],\n});\n\nr.text;       // string | null  — final assistant text (null if ended on a tool round)\nr.toolCalls;  // ToolCallRecord[] — every tool call dispatched, in order:\n              //   { name, args, result, isError, source: \"local\" | \"mcp\" }\nr.messages;   // ChatMessage[] — full conversation (system + user + assistant + tool)\nr.rounds;     // number — model rounds executed\nr.usage;      // { prompt_tokens, completion_tokens, total_tokens } — aggregated\n```\n\n`maxRounds` (default `10`) caps the loop so a misbehaving model can't burn\ntokens forever. When hit, the run returns with whatever's accumulated; no\nexception.\n\n### `defineTool` — Zod + JSON Schema\n\nTwo overloads. Pick whichever fits your project.\n\n**Zod path** (recommended) — handler args are typed via `z.infer`:\n\n```ts\nimport { z } from \"zod\";\n\nconst send_email = defineTool({\n  description: \"Send a transactional email.\",\n  parameters: z.object({\n    to:      z.string().email(),\n    subject: z.string().min(1).max(100),\n    body:    z.string(),\n    cc:      z.array(z.string().email()).optional(),\n  }),\n  handler: async ({ to, subject, body, cc }) => {\n    //                ^^^^^^^^^^^^^^^^^^^^^^\n    //                all typed; Zod .parse() runs first\n    return await emailService.send({ to, subject, body, cc });\n  },\n});\n```\n\nThe schema's `.parse()` runs on the model's args **before** your handler\nsees them — invalid objects throw early. The schema is also converted to\nJSON Schema (via the optional peer dep `zod-to-json-schema`, lazy-imported,\nWeakMap-cached) and exposed to the model.\n\n**JSON Schema path** — for callers without Zod:\n\n```ts\nconst send_email = defineTool({\n  description: \"Send a transactional email.\",\n  parameters: {\n    type: \"object\",\n    required: [\"to\", \"subject\", \"body\"],\n    properties: {\n      to:      { type: \"string\", format: \"email\" },\n      subject: { type: \"string\", maxLength: 100 },\n      body:    { type: \"string\" },\n      cc:      { type: \"array\", items: { type: \"string\", format: \"email\" } },\n    },\n  },\n  handler: async (args) => {\n    const { to, subject, body, cc } = args as { to: string; subject: string; body: string; cc?: string[] };\n    return await emailService.send({ to, subject, body, cc });\n  },\n});\n```\n\n### Hybrid local + MCP tools\n\nLocal tools run in your process; MCP tools run on the server. The agent\nauto-routes either way based on the tool name (MCP tools are prefixed\n`mcp__<connector>__<tool>`).\n\n```ts\nimport { CanaAgent, defineTool } from \"@cana-ai/sdk/openai\";\nimport { z } from \"zod\";\n\nconst fill_form = defineTool({\n  description: \"Fill any subset of onboarding-form fields.\",\n  parameters: z.object({\n    companyName: z.string().optional(),\n    teamSize:    z.number().int().min(1).optional(),\n    priority:    z.enum([\"low\", \"medium\", \"high\", \"urgent\"]).optional(),\n  }),\n  handler: async (patch) => {\n    formStore.update(patch);                    // mutate your UI state\n    return { ok: true, form: formStore.snapshot() };\n  },\n});\n\nconst agent = new CanaAgent({\n  appId, apiKey,\n  model:        \"Anthropic/claude-haiku-4-5-20251001\",\n  systemPrompt: \"You're an onboarding assistant.\",\n  tools:        { fill_form },                  // local\n  mcp:          [\"slack\"],                      // server-hosted\n});\n\nconst r = await agent.run({\n  prompt: \"Onboard Acme (12 ppl, high priority), then post to #onboarding.\",\n});\n\n// r.toolCalls includes BOTH:\n//   { name: \"fill_form\",                       source: \"local\", result: { ok: true, form: {…} } }\n//   { name: \"mcp__slack__send_message\",        source: \"mcp\",   result: { … } }\n```\n\n### `agent.stream` — streamed events\n\nSame arguments as `run`, returns an `AsyncIterable<AgentEvent>`.\n\n```ts\nfor await (const ev of agent.stream({ prompt: \"Tokyo weather?\" })) {\n  switch (ev.type) {\n    case \"round_start\":  console.log(`[round ${ev.round}]`); break;\n    case \"text_delta\":   process.stdout.write(ev.delta); break;            // token-by-token\n    case \"tool_call\":    console.log(`→ ${ev.source}:${ev.name}(${JSON.stringify(ev.args)})`); break;\n    case \"tool_result\":  console.log(`✓ ${ev.name} ${ev.isError ? \"FAILED\" : \"ok\"}`); break;\n    case \"done\":         console.log(\"\\nfinal:\", ev.text, \"usage:\", ev.usage); break;\n    case \"error\":        console.error(\"error:\", ev.code, ev.message); break;\n  }\n}\n```\n\nEvent shapes:\n\n```ts\ntype AgentEvent =\n  | { type: \"round_start\"; round: number }\n  | { type: \"text_delta\";  delta: string; round: number }\n  | { type: \"tool_call\";   name: string; args: unknown; source: \"local\" | \"mcp\"; round: number }\n  | { type: \"tool_result\"; name: string; result: unknown; isError: boolean; source: \"local\" | \"mcp\"; round: number }\n  | { type: \"done\";        text: string | null; toolCalls: ToolCallRecord[]; messages: ChatMessage[]; usage: …  }\n  | { type: \"error\";       code: string; message: string; status?: number };\n```\n\n`text_delta` events stream the model's text token-by-token (using the SDK's\nchunk aggregator). Tool-call deltas split across multiple OpenAI chunks are\nreassembled before they're dispatched, so you get one `tool_call` event per\nactual call, not one per partial chunk.\n\n### `AgentSession` — multi-turn memory\n\n```ts\nconst s = agent.session();\nconst r1 = await s.send(\"My favorite city is Reykjavik.\");\nconst r2 = await s.send(\"What's my favorite city?\");\nconsole.log(r2.text);                       // \"Reykjavik.\"\n\ns.messages;                                 // ChatMessage[] — read-only\ns.clear();                                  // reset to empty\n\n// streaming variant:\nfor await (const ev of s.stream(\"Tell me a story.\")) { /* ... */ }\n```\n\nEach `send` carries the full prior history forward; the session stores\nwhatever the agent returns (system + user + assistant + tool messages\ntogether).\n\n### Handler errors are captured, not thrown\n\nIf your tool handler throws, the agent records the error as the tool\nresult and continues the loop. The model sees a JSON error blob and can\neither retry, switch tools, or apologise to the user.\n\n```ts\nconst broken = defineTool({\n  description: \"Always fails — for testing.\",\n  parameters: z.object({ x: z.string() }),\n  handler: async () => { throw new Error(\"kaboom\"); },\n});\n\nconst r = await agent.run({ prompt: \"Call broken with x='hi'.\" });\nr.toolCalls[0];\n//   {\n//     name:    \"broken\",\n//     args:    { x: \"hi\" },\n//     result:  { error: \"kaboom\" },\n//     isError: true,\n//     source:  \"local\",\n//   }\n```\n\n---\n\n## Low-level client: `CanaClient`\n\nWhen you want to drive the loop yourself, or only need the wire-level\nhelpers (auth, streaming, MCP RPC).\n\n```ts\nimport { CanaClient } from \"@cana-ai/sdk/openai\";\n\nconst client = new CanaClient({\n  appId:      \"app_…\",\n  apiKey:     process.env.CANA_APP_KEY!,     // cak_live_… (or getToken / bearerToken)\n  apiBase:    \"https://cana.build\",          // optional, this is the default\n});\n```\n\n### Chat completions\n\n```ts\nconst c = await client.chat.completions.create({\n  model:       \"OpenAI/gpt-4o-mini\",\n  messages:    [{ role: \"user\", content: \"Summarize TCP slow-start in 3 bullets.\" }],\n  temperature: 0.2,\n  max_tokens:  300,\n});\n\nconsole.log(c.choices[0].message.content);\nconsole.log(\"usage:\", c.usage);\n```\n\nRequest fields supported: `model`, `messages`, `tools`, `tool_choice`,\n`response_format`, `stream`, `max_tokens`, `temperature`, `top_p`, `stop`.\n\n### Streaming\n\n```ts\nconst stream = await client.chat.completions.create({\n  model:    \"OpenAI/gpt-4o-mini\",\n  messages: [{ role: \"user\", content: \"Write a haiku about TCP.\" }],\n  stream:   true,\n});\n\nfor await (const chunk of stream) {\n  const delta = chunk.choices[0]?.delta;\n  if (delta?.content) process.stdout.write(delta.content);\n  if (chunk.choices[0]?.finish_reason) console.log(\"\\n[done]\", chunk.usage);\n}\n```\n\nTypeScript narrows the return shape on `stream`:\n\n```ts\n// stream: true  → AsyncIterable<ChatCompletionChunk>\n// stream: false → ChatCompletion\n```\n\n### Function tools — manual dispatch loop\n\nOpenAI tool format works as-is. The low-level client doesn't dispatch\nclient tools for you — you do it. (Or use `CanaAgent` above and skip this.)\n\n```ts\nconst tools = [{\n  type: \"function\",\n  function: {\n    name: \"lookup_weather\",\n    description: \"Current temperature in °C for a city.\",\n    parameters: { type: \"object\", required: [\"city\"], properties: { city: { type: \"string\" } } },\n  },\n}];\n\nlet messages = [{ role: \"user\", content: \"What's it like in Tokyo?\" }];\nfor (let i = 0; i < 5; i++) {\n  const c = await client.chat.completions.create({ model: \"OpenAI/gpt-4o-mini\", messages, tools });\n  const msg = c.choices[0].message;\n  messages.push(msg);\n  if (!msg.tool_calls?.length) break;\n  for (const call of msg.tool_calls) {\n    const args = JSON.parse(call.function.arguments);\n    const result = await lookupWeather(args.city);\n    messages.push({ role: \"tool\", tool_call_id: call.id, content: JSON.stringify(result) });\n  }\n}\n```\n\n### MCP federation — `runWithMcp` auto-dispatch\n\n```ts\nconst r = await client.runWithMcp({\n  model:    \"Anthropic/claude-haiku-4-5-20251001\",\n  messages: [{ role: \"user\", content: \"Find last week's PRs in cana-web.\" }],\n  mcp:      [\"github\", \"search\"],\n  maxRounds: 5,\n});\nconsole.log(r.message.content);\nconsole.log(r.rounds);\n```\n\n`runWithMcp` discovers + dispatches `mcp__*` calls server-side, and EXITS\nthe loop when the model calls a non-MCP tool (so the caller can dispatch\nlocal tools, append the result, and call again). For an integrated\nlocal+MCP loop, prefer [`CanaAgent`](#prebuilt-agent-canaagent).\n\n### MCP federation — manual\n\n```ts\nconst { tools, dropped } = await client.mcp.tools([\"github\"]);\n//   tools:   OpenAI-format ChatTool[] you pass to chat.completions.create\n//   dropped: connector names the App owner doesn't have enabled\n\nconst c = await client.chat.completions.create({ model, messages, tools });\n\nfor (const call of c.choices[0].message.tool_calls ?? []) {\n  if (!call.function.name.startsWith(\"mcp__\")) continue;\n  const out = await client.mcp.execute({\n    name:      call.function.name,\n    arguments: JSON.parse(call.function.arguments),\n  });\n  // out.result / out.isError / out.durationMs\n}\n```\n\n---\n\n## Structured output (JSON Schema)\n\n```ts\nconst c = await client.chat.completions.create({\n  model:    \"OpenAI/gpt-4o-mini\",\n  messages: [{ role: \"user\", content: \"Give me one US state with capital + population.\" }],\n  response_format: {\n    type: \"json_schema\",\n    json_schema: {\n      name:   \"USState\",\n      strict: true,\n      schema: {\n        type: \"object\",\n        required: [\"name\", \"capital\", \"population\"],\n        properties: {\n          name:       { type: \"string\" },\n          capital:    { type: \"string\" },\n          population: { type: \"integer\", minimum: 0 },\n        },\n      },\n    },\n  },\n});\n\nconst parsed = JSON.parse(c.choices[0].message.content!);\n```\n\n## Structured output (Zod helper)\n\n```ts\nimport { z } from \"zod\";\n\nconst Person = z.object({\n  name:       z.string(),\n  age:        z.number().int(),\n  occupation: z.string(),\n});\n\nconst { result, raw } = await client.chat.completions.generate({\n  model:    \"OpenAI/gpt-4o-mini\",\n  messages: [{ role: \"user\", content: \"Anna is a 34-year-old marine biologist.\" }],\n  schema:   Person,\n});\n\nresult.name;        // \"Anna\"\nresult.age;         // 34\nraw.usage;          // full ChatCompletion if you need it\n```\n\nOptional peer dep: `zod-to-json-schema` (auto-installed in Node; bundle it\nyourself in browser builds).\n\n## System prompts & multi-turn\n\nFor `CanaAgent` use `systemPrompt` + the built-in `session()`. For\n`CanaClient`, just include `{role: \"system\", …}` messages and keep\nappending:\n\n```ts\nconst messages = [\n  { role: \"system\", content: \"You are a terse senior backend engineer.\" },\n  { role: \"user\",   content: \"When is a UNIQUE index worse than a CHECK constraint?\" },\n];\n\nconst r1 = await client.chat.completions.create({ model, messages });\nmessages.push(r1.choices[0].message);\nmessages.push({ role: \"user\", content: \"Give a concrete example.\" });\nconst r2 = await client.chat.completions.create({ model, messages });\n```\n\n## Usage / cost tracking\n\nEvery non-streaming response includes `usage`. Streaming responses include\nit on the final chunk (OpenAI / Moonshot / Kimi / DeepSeek do).\n`CanaAgent.run` aggregates across all rounds for you.\n\n```ts\nconst c = await client.chat.completions.create({ model, messages });\nconst { prompt_tokens, completion_tokens, total_tokens } = c.usage!;\n```\n\n## Custom `fetch` (testing, proxies, telemetry)\n\nOverride the network layer for unit tests, edge-network proxies, request\nmirroring, or tracing. Works on both `CanaAgent` and `CanaClient`.\n\n```ts\nconst agent = new CanaAgent({\n  appId, apiKey, model: \"OpenAI/gpt-4o-mini\",\n  fetch: async (url, init) => {\n    console.log(\"→\", init?.method, url);\n    const t0 = Date.now();\n    const res = await globalThis.fetch(url, init);\n    console.log(\"←\", res.status, `${Date.now() - t0}ms`);\n    return res;\n  },\n});\n```\n\n## Hosted page / embed: `CanaIssuer`\n\nIf your product hosts a Cana embed widget or sends users to the hosted\npage (`cana.build/a/<slug>`), you typically want visitors to land\n**already authenticated as themselves**, not as anonymous traffic.\n\n`CanaIssuer` is the server-side helper for that flow:\n\n```ts\nimport { CanaIssuer } from \"@cana-ai/sdk/openai\";\n\nconst issuer = new CanaIssuer({\n  appId:      \"app_…\",\n  signingKey: process.env.CANA_APP_ISSUER_KEY!,   // csk_live_… — issuer-purpose key\n  kid:        process.env.CANA_APP_ISSUER_KID,    // optional; defaults to appId\n  apiBase:    \"https://cana.build\",\n});\n\n// Inside your issuer-URL endpoint (Next.js route handler, Express, …):\nconst { code } = await issuer.issueExchangeCode({\n  user: { sub: user.id, email: user.email, name: user.name },\n  // ttlSec: 30,        // JWT lifetime (clamped to 1–300; default 30)\n  // codeTtlSec: 60,    // exchange-code TTL on Cana's side (default 30)\n});\n\nreturn Response.redirect(issuer.buildReturnUrl(returnTo, code));\n//      ^ equivalent to `${returnTo}?exchange=<code>`\n```\n\n**Key separation.** Use a dedicated **\"issuer\"-purpose** `AppSigningKey`\n(`csk_live_…`), separate from the Bearer App API key (`cak_live_…`) the SDK\nuses for `CanaClient` / `CanaAgent` auth. Cana's verifier filters by purpose,\nso rotating one credential doesn't break the other. Mint the issuer key from\nyour App dashboard or via the cana-web admin path.\n\n### Just the JWT\n\n```ts\nconst jwt = await issuer.signJwt({\n  user: { sub: \"user_123\", email: \"alice@example.com\", name: \"Alice\" },\n  ttlSec: 60,\n});\n```\n\nThe JWT is HS256 with header `{alg, typ, kid}` and claims\n`{sub, email?, name?, metadata?, iss, aud, iat, exp, jti}` — matches\nwhat Cana's `verifyAppToken` accepts.\n\n### End-to-end visitor flow\n\n```\n1. User opens             https://cana.build/a/<slug>\n2. Cana 302s →            https://your.app/issuer?return_to=<encoded>\n3. Your endpoint:\n     - checks your session cookie\n     - (optional) issuer.issueExchangeCode({ user })\n     - 302 to issuer.buildReturnUrl(returnTo, code)\n4. Cana page redeems the code via /exchange/redeem,\n   drops a session cookie, page is authenticated.\n```\n\n### Direct API key vs issuer flow at a glance\n\n| | Direct API key (`CanaClient` / `CanaAgent`) | Issuer flow (`CanaIssuer`) |\n| --- | --- | --- |\n| Triggered by | Your server code | A user visiting a Cana URL |\n| Identity | Anonymous (App-level) | End user (`sub` claim) |\n| Key | **App API key** (`cak_live_…`) | **\"issuer\"**-purpose AppSigningKey (`csk_live_…`) |\n| Auth | Bearer App API key (`cak_live_…`) | HS256 JWT + one HMAC per exchange |\n| Browser sees key | No | No |\n| Sets Cana session cookie | No | Yes |\n| Who renders the UI | You do | Cana does |\n\n## Browser — `<script>` tag\n\nA pre-built IIFE bundle is served from your Cana instance and falls through\nto jsDelivr for CDN edge caching. Exposes `CanaOpenAI.CanaAgent`,\n`CanaOpenAI.defineTool`, `CanaOpenAI.CanaClient`, etc.\n\n```html\n<script src=\"https://cana.build/sdk/openai.js\"></script>\n<script>\n  const agent = new CanaOpenAI.CanaAgent({\n    appId:    \"app_…\",\n    // Fetch a short-lived JWT from YOUR backend (minted there with CanaIssuer).\n    // The SDK caches it, auto-refreshes before exp, and re-fetches once on a 401.\n    getToken: () => fetch(\"/my-backend/cana-jwt\").then(r => r.text()),\n    model:    \"OpenAI/gpt-4o-mini\",\n  });\n\n  agent.run({ prompt: \"Hello\" })\n    .then(r => console.log(r.text));\n</script>\n```\n\n> ✅ With `getToken`, no long-lived secret ever reaches page source — your\n> backend mints a short-lived JWT per session and the SDK refreshes it\n> automatically. This is the safe browser pattern. **Do not** paste a static\n> `apiKey` (`cak_live_…`) into client-side code: anyone who views source can\n> read it.\n\n## Plain OpenAI SDK against the proxy\n\nThe proxy authenticates with a standard `Authorization: Bearer` header, so any\nOpenAI client (`openai-node`, `openai-python`, the AI SDK from Vercel, etc.)\nworks out of the box — no fetch wrapper, no signing. URL shape:\n\n```\nhttps://cana.build/api/v1/apps/<appId>/openai/chat/completions\n```\n\nJust pass your App API key as the client's `apiKey`; the OpenAI SDK sends it\nas `Authorization: Bearer …` for you — example using `openai-node` 4.x:\n\n```ts\nimport OpenAI from \"openai\";\n\nconst APP_ID = \"app_…\";\n\nconst openai = new OpenAI({\n  baseURL: `https://cana.build/api/v1/apps/${APP_ID}/openai`,\n  apiKey:  process.env.CANA_APP_KEY!,        // cak_live_… → sent as Authorization: Bearer\n});\n```\n\n## Low-level: `signRequest`\n\nA generic **HMAC-SHA256** helper, still exported. As of v1.0.0 it is **not\nused for proxy auth** — the proxy authenticates with `Authorization: Bearer`\n(see [Authentication](#authentication)), so you don't need this for normal\nSDK or plain-OpenAI usage. It remains available if you have your own\nHMAC-signing needs (e.g. webhooks). It is the same primitive `CanaIssuer`\nuses internally for the `/exchange/issue` exchange.\n\n```ts\nimport { signRequest } from \"@cana-ai/sdk/openai\";\n\nconst { signatureHeader, timestamp, signatureHex } = await signRequest({\n  signingKey: \"csk_live_…\",\n  rawBody:    JSON.stringify({ … }),\n  // timestampMs: 1700000000000,  // override for deterministic tests\n});\n```\n\n## Low-level: `parseChunkStream`\n\nParses an OpenAI-style SSE body into `AsyncIterable<ChatCompletionChunk>`.\nHandles partial-buffer reassembly, comment heartbeats, and the `[DONE]`\nsentinel automatically.\n\n```ts\nimport { parseChunkStream } from \"@cana-ai/sdk/openai\";\n\nconst res = await fetch(url, { method: \"POST\", headers, body });\nfor await (const chunk of parseChunkStream(res.body!)) {\n  process.stdout.write(chunk.choices[0]?.delta.content ?? \"\");\n}\n```\n\n## Reliability: retries, timeouts, backoff\n\n**You do not need to write a retry wrapper. The client already does this.**\n\nEvery request made through `CanaClient` (and therefore `CanaAgent`,\n`runWithMcp`, streaming and the structured-output helpers) is:\n\n- **Retried** when the failure is worth retrying — HTTP 429, any 5xx, and\n  network-level failures (DNS, TLS, connection reset, timeout). A 4xx the\n  server marks non-retryable fails immediately; retrying it would only be\n  slower, not more successful.\n- **Time-bounded per attempt.** A hung connection is aborted rather than\n  hanging your process forever.\n- **Backed off** between attempts, honouring the server's `Retry-After`\n  header — both the delta-seconds and the HTTP-date forms — capped at 30s so\n  a bad value cannot park your process.\n\nDefaults, both overridable:\n\n```ts\nconst client = new CanaClient({\n  appId: \"app_abc\",\n  apiKey: process.env.CANA_APP_KEY!,\n  timeoutMs: 180_000,   // per ATTEMPT (default 3 min)\n  maxAttempts: 3,       // total, including the first (default 3)\n});\n```\n\nPass `maxAttempts: 1` to disable retries — useful in tests that assert error\nshape, where the backoff would otherwise dominate the runtime.\n\n### Per-request options\n\n`chat.completions.create(body, options?)` accepts transport settings separately\nfrom the JSON body, for both normal and streaming completions:\n\n```ts\nconst controller = new AbortController();\nconst completion = await client.chat.completions.create({ model, messages }, {\n  signal: controller.signal,\n  timeoutMs: 30_000,\n  maxAttempts: 2,\n  headers: { \"X-Request-Source\": \"support\" }, // also Headers or [name, value][]\n  idempotencyKey: \"support-request-123\",\n});\n```\n\nThese overrides affect only this call, not client defaults. `timeoutMs` must be\na safe integer >= 0 (zero schedules an immediate timeout); `maxAttempts` must be\na safe integer >= 1. The historical single JWT refresh on 401 still applies even\nwith `maxAttempts: 1`.\n\n**Cancellation and timeout work together.** The signal cancels credential waits,\nfetches, response consumption and retry backoff, preserving its abort reason.\nDeliberate cancellation is never retried. The per-attempt timeout remains active\nthrough response consumption; network/timeout failures before response headers\ncan retry, but body failures and partially consumed streams are not replayed.\n\nHeaders are merged case-insensitively. Authorization, Content-Type and Accept\nremain SDK-owned. An explicit `idempotencyKey` overrides the corresponding header\nand is reused unchanged across retries and JWT refresh. The SDK does not generate\nkeys or guarantee server-side deduplication.\n\n### What you get when it finally fails\n\nA `CanaApiError` carrying a **typed `code`**, the HTTP `status`, whether it was\n`retryable`, and `retryAfterSec` when the server supplied one. Unless explicitly\noverridden using `errorMessages`, the `message` is the server's own text when it\nsent one — Cana attaches specific, actionable\nguidance to many errors, and that is preserved verbatim rather than replaced\nwith a generic sentence. For bare codes the SDK supplies actionable text of its\nown: Cana's failures are mostly *configuration* problems (`app_paused`,\n`model_not_found`, `billing_exceeded`), and \"HTTP 402\" tells a user nothing\nabout what to fix.\n\nThe error code is read from the response body, falling back to the\n`Cana-Reason` header when the body cannot be parsed — a proxy 502 or a\ntruncated response still names its reason.\n\n```ts\ntry {\n  await client.chat.completions.create({ model, messages });\n} catch (e) {\n  if (e instanceof CanaApiError) {\n    console.error(e.code, e.status, e.message);  // already actionable\n  }\n}\n```\n\n### Custom error messages\n\n```ts\nimport { CanaClient, ERROR_MESSAGES } from \"@cana-ai/sdk/openai\";\n\nconst client = new CanaClient({\n  appId,\n  getToken,\n  errorMessages: { app_paused: \"Contact your administrator to resume this app.\" },\n});\nconsole.log(ERROR_MESSAGES.app_paused); // exported, frozen built-in guidance\n```\n\nOverrides are copied and frozen per client and apply to chat and MCP errors.\nMessage precedence is: explicit override, server message, built-in default, HTTP\nfallback. Unknown custom codes are supported; modifying the input object later\ncannot affect the client or other clients.\n\n## Errors\n\nAll non-2xx responses throw `CanaApiError` with `.status` and a typed\n`.code`. Common codes:\n\n| code | HTTP | meaning |\n| --- | --- | --- |\n| `signature_invalid` | 401 | wrong key or malformed header |\n| `signature_expired` | 401 | timestamp outside 5-min skew |\n| `signature_replayed` | 401 | nonce already seen |\n| `idempotency_conflict` | 409 | same key + different body |\n| `app_paused` | 403 | `App.status != LIVE` |\n| `rate_limited` | 429 | retried automatically, honouring `Retry-After` |\n| `billing_exceeded` | 402 | App monthly cap hit |\n| `unknown_provider` | 400 | no `LlmProviderConfig` with that name |\n| `model_not_found` | 400 | config exists but doesn't host the model |\n| `request_invalid` | 400 | Zod-rejected request body |\n| `response_format_unsupported_for_provider` | 400 | Anthropic + strict JSON schema |\n| `mcp_connector_unknown` | 400 | connector not enabled on this App |\n| `mcp_dispatch_failed` | 502 | MCP server returned an error |\n| `upstream_error` | 502 | LLM provider failed |\n| `internal` | 500 | unhandled |\n\n```ts\nimport { CanaApiError } from \"@cana-ai/sdk/openai\";\n\ntry {\n  await agent.run({ prompt: \"...\" });\n} catch (e) {\n  if (!(e instanceof CanaApiError)) throw e;\n  if (e.code === \"rate_limited\") { /* already retried + backed off for you */ }\n  if (e.code === \"billing_exceeded\") { /* upgrade plan */ }\n}\n```\n\nIn `agent.stream`, errors surface as a terminal `{type:\"error\", code, message, status}` event instead.\n\n## TypeScript exports\n\n```ts\nimport {\n  // High-level agent\n  CanaAgent, AgentSession, defineTool,\n  type CanaAgentOptions, type AgentTool, type AgentToolContext,\n  type AgentRunArgs, type AgentRunResult, type AgentEvent, type ToolCallRecord,\n\n  // Hosted-page / embed issuer\n  CanaIssuer,\n  type CanaIssuerOptions, type CanaIssuerUser,\n  type SignJwtArgs, type IssueExchangeCodeArgs, type ExchangeCodeResult,\n\n  // Low-level client\n  CanaClient, ERROR_MESSAGES, type CanaClientOptions, type CanaRequestOptions,\n  signRequest, parseChunkStream,\n\n  // Errors + wire types\n  CanaApiError,\n  type ChatMessage, type ChatTool, type ChatToolCall, type ChatToolChoice,\n  type ChatCompletion, type ChatCompletionChunk, type ChatCompletionRequest,\n  type ResponseFormat, type ResponseFormatJsonSchema,\n  type CanaErrorBody,\n} from \"@cana-ai/sdk/openai\";\n```\n\nBoth `@cana-ai/sdk` and `@cana-ai/sdk/openai` ship ESM and CommonJS runtime\nartifacts with matching TypeScript declarations. CommonJS consumers (including\nJest configurations that use `require`) can load the client without transforming\nES modules:\n\n```js\nconst { CanaClient, ERROR_MESSAGES } = require(\"@cana-ai/sdk/openai\");\n```\n\n## Web Crypto only\n\nZero `node:crypto` import at compile time — bundles cleanly for Node 18+,\nBun, Deno, Cloudflare Workers, Vercel Edge, and modern browsers.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}