{"_id":"@k2b/nessi","_rev":"6-0b2913be576ae0b9f6555eed4b455973","name":"@k2b/nessi","dist-tags":{"latest":"0.12.1"},"versions":{"0.10.0-rc.0":{"name":"@k2b/nessi","version":"0.10.0-rc.0","keywords":["ai","llm","agent","tools","streaming","openai","openrouter","ollama","anthropic","gemini","mistral"],"author":{"name":"Valentin Kolb"},"license":"MIT","_id":"@k2b/nessi@0.10.0-rc.0","maintainers":[{"name":"valentinkolb","email":"mail@valentin-kolb.com"}],"homepage":"https://github.com/k2b-dev/nessi#readme","bugs":{"url":"https://github.com/k2b-dev/nessi/issues"},"dist":{"shasum":"755cc276532e855fd5ba19ac9a0568a9a845dcf6","tarball":"https://registry.npmjs.org/@k2b/nessi/-/nessi-0.10.0-rc.0.tgz","fileCount":69,"integrity":"sha512-FpMV/4WK2F2LDx6tofozDfw42iFtYhav3zaalbl6FBBh75IJPW7bRKseu/VvJxHXRkdfjay+zbcEbeX6RU5hpA==","signatures":[{"sig":"MEYCIQCL0IdlsDMM7fIAt/5hUDgxWp6MUR7I6QObpQPLoX0bVAIhAJSdJUa0v0MoObz/K/D5kfRc+wzSq996gFKDkjQG9Hth","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":224726},"main":"index.js","type":"module","types":"index.d.ts","module":"index.js","exports":{".":{"types":"./index.d.ts","import":"./index.js"},"./ai":{"types":"./ai/index.d.ts","import":"./ai/index.js"},"./ai/providers/vllm":{"types":"./ai/providers/vllm.d.ts","import":"./ai/providers/vllm.js"},"./ai/providers/gemini":{"types":"./ai/providers/gemini.d.ts","import":"./ai/providers/gemini.js"},"./ai/providers/ollama":{"types":"./ai/providers/ollama.d.ts","import":"./ai/providers/ollama.js"},"./ai/providers/openai":{"types":"./ai/providers/openai.d.ts","import":"./ai/providers/openai.js"},"./ai/providers/mistral":{"types":"./ai/providers/mistral.d.ts","import":"./ai/providers/mistral.js"},"./ai/providers/anthropic":{"types":"./ai/providers/anthropic.d.ts","import":"./ai/providers/anthropic.js"},"./ai/providers/openrouter":{"types":"./ai/providers/openrouter.d.ts","import":"./ai/providers/openrouter.js"},"./ai/providers/openai-compatible":{"types":"./ai/providers/openai-compatible.d.ts","import":"./ai/providers/openai-compatible.js"}},"_npmUser":{"name":"valentinkolb","email":"mail@valentin-kolb.com"},"repository":{"url":"git+https://github.com/k2b-dev/nessi.git","type":"git"},"_npmVersion":"12.0.1","description":"Minimal agent loop and provider adapters for nessi.","directories":{},"sideEffects":false,"_nodeVersion":"24.15.0","dependencies":{"zod":"^4.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/nessi_0.10.0-rc.0_1785085330508_0.5726107429601595","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"@k2b/nessi","version":"0.10.0","keywords":["ai","llm","agent","tools","streaming","openai","openrouter","ollama","anthropic","gemini","mistral"],"author":{"name":"Valentin Kolb"},"license":"MIT","_id":"@k2b/nessi@0.10.0","maintainers":[{"name":"valentinkolb","email":"mail@valentin-kolb.com"}],"homepage":"https://github.com/k2b-dev/nessi#readme","bugs":{"url":"https://github.com/k2b-dev/nessi/issues"},"dist":{"shasum":"a39182908c8a99b492d5990ff599bad8783a18be","tarball":"https://registry.npmjs.org/@k2b/nessi/-/nessi-0.10.0.tgz","fileCount":69,"integrity":"sha512-iHeqOH6Rb/+XutRXc6tJ4NjygCMe4A7TrV05f7YGwCe5U3am+3u9zJ3efMriDeCwCr4WJxwxv5s4Ig0RszQ3Tg==","signatures":[{"sig":"MEUCIQDKovqHcLxqkbTOwQ7yu3N/GNAoUbeew/Vyc1TcoVZjVAIgfPT/Fo7uFUEULFagMrIBOkVofLH2XUvW+e81Rjm5iUM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@k2b%2fnessi@0.10.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":224721},"main":"index.js","type":"module","types":"index.d.ts","module":"index.js","exports":{".":{"types":"./index.d.ts","import":"./index.js"},"./ai":{"types":"./ai/index.d.ts","import":"./ai/index.js"},"./ai/providers/vllm":{"types":"./ai/providers/vllm.d.ts","import":"./ai/providers/vllm.js"},"./ai/providers/gemini":{"types":"./ai/providers/gemini.d.ts","import":"./ai/providers/gemini.js"},"./ai/providers/ollama":{"types":"./ai/providers/ollama.d.ts","import":"./ai/providers/ollama.js"},"./ai/providers/openai":{"types":"./ai/providers/openai.d.ts","import":"./ai/providers/openai.js"},"./ai/providers/mistral":{"types":"./ai/providers/mistral.d.ts","import":"./ai/providers/mistral.js"},"./ai/providers/anthropic":{"types":"./ai/providers/anthropic.d.ts","import":"./ai/providers/anthropic.js"},"./ai/providers/openrouter":{"types":"./ai/providers/openrouter.d.ts","import":"./ai/providers/openrouter.js"},"./ai/providers/openai-compatible":{"types":"./ai/providers/openai-compatible.d.ts","import":"./ai/providers/openai-compatible.js"}},"gitHead":"1ec6c032617c232e913c8b6eabe0a23520eebc38","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:51e1b19c-686e-4918-851c-842c23eef091"}},"repository":{"url":"git+https://github.com/k2b-dev/nessi.git","type":"git"},"_npmVersion":"11.5.1","description":"Minimal agent loop and provider adapters for nessi.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.1","dependencies":{"zod":"^4.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/nessi_0.10.0_1785085462229_0.05086412384200756","host":"s3://npm-registry-packages-npm-production"}},"0.11.0":{"name":"@k2b/nessi","version":"0.11.0","keywords":["ai","llm","agent","tools","streaming","openai","openrouter","ollama","anthropic","gemini","mistral"],"author":{"name":"Valentin Kolb"},"license":"MIT","_id":"@k2b/nessi@0.11.0","maintainers":[{"name":"valentinkolb","email":"mail@valentin-kolb.com"}],"homepage":"https://github.com/k2b-dev/nessi#readme","bugs":{"url":"https://github.com/k2b-dev/nessi/issues"},"dist":{"shasum":"9cfcbb0b0c7a24ad6903f6ce763bb714c3ca529e","tarball":"https://registry.npmjs.org/@k2b/nessi/-/nessi-0.11.0.tgz","fileCount":69,"integrity":"sha512-SajlKjEJQvM209JqFQNpGlGhOIj6z3U13NbgDYBx4snhbxxRXtCO3qNseNWMT5OgjqkWvrp4M84tP2nWZAC1Eg==","signatures":[{"sig":"MEQCIFCzU/GYlXH17UU3GzgJiwTSm8d8Tr2pRn0Y61bA26tJAiAiGwuOJU76MzHBhKUMYqz7m39tF7602g61uRhGQvRFOw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@k2b%2fnessi@0.11.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":228664},"main":"index.js","type":"module","types":"index.d.ts","module":"index.js","exports":{".":{"types":"./index.d.ts","import":"./index.js"},"./ai":{"types":"./ai/index.d.ts","import":"./ai/index.js"},"./ai/providers/vllm":{"types":"./ai/providers/vllm.d.ts","import":"./ai/providers/vllm.js"},"./ai/providers/gemini":{"types":"./ai/providers/gemini.d.ts","import":"./ai/providers/gemini.js"},"./ai/providers/ollama":{"types":"./ai/providers/ollama.d.ts","import":"./ai/providers/ollama.js"},"./ai/providers/openai":{"types":"./ai/providers/openai.d.ts","import":"./ai/providers/openai.js"},"./ai/providers/mistral":{"types":"./ai/providers/mistral.d.ts","import":"./ai/providers/mistral.js"},"./ai/providers/anthropic":{"types":"./ai/providers/anthropic.d.ts","import":"./ai/providers/anthropic.js"},"./ai/providers/openrouter":{"types":"./ai/providers/openrouter.d.ts","import":"./ai/providers/openrouter.js"},"./ai/providers/openai-compatible":{"types":"./ai/providers/openai-compatible.d.ts","import":"./ai/providers/openai-compatible.js"}},"gitHead":"00035585bd6663d731eb1b917c5f806c492f012f","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:51e1b19c-686e-4918-851c-842c23eef091"}},"repository":{"url":"git+https://github.com/k2b-dev/nessi.git","type":"git"},"_npmVersion":"11.5.1","description":"Minimal agent loop and provider adapters for nessi.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.1","dependencies":{"zod":"^4.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/nessi_0.11.0_1785622227188_0.28108037904867156","host":"s3://npm-registry-packages-npm-production"}},"0.12.0":{"name":"@k2b/nessi","version":"0.12.0","keywords":["ai","llm","agent","tools","streaming","openai","openrouter","ollama","anthropic","gemini","mistral"],"author":{"name":"Valentin Kolb"},"license":"MIT","_id":"@k2b/nessi@0.12.0","maintainers":[{"name":"valentinkolb","email":"mail@valentin-kolb.com"}],"homepage":"https://github.com/k2b-dev/nessi#readme","bugs":{"url":"https://github.com/k2b-dev/nessi/issues"},"dist":{"shasum":"5e0613651a04261e80dc72398dd1bf11b46df2a2","tarball":"https://registry.npmjs.org/@k2b/nessi/-/nessi-0.12.0.tgz","fileCount":73,"integrity":"sha512-1yxW7UWmpeoUTw8hvYrm5IF/8YwZLzcBwbczzAvYxqSfZ/k9xHn0dDvoo8P7eHnj82P4TlsY85xUQ2NJHuj6Vg==","signatures":[{"sig":"MEUCIDuYe+I9jGZj3rmPvPPS5a18fLiWah+J9ggCp6pi+dtzAiEA+CHlrVEl65lFvdxdKUlZdFhgcaluUCQjCZE5F+6x3no=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@k2b%2fnessi@0.12.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":235320},"main":"index.js","type":"module","types":"index.d.ts","module":"index.js","exports":{".":{"types":"./index.d.ts","import":"./index.js"},"./ai":{"types":"./ai/index.d.ts","import":"./ai/index.js"},"./ai/providers/vllm":{"types":"./ai/providers/vllm.d.ts","import":"./ai/providers/vllm.js"},"./ai/providers/gemini":{"types":"./ai/providers/gemini.d.ts","import":"./ai/providers/gemini.js"},"./ai/providers/ollama":{"types":"./ai/providers/ollama.d.ts","import":"./ai/providers/ollama.js"},"./ai/providers/openai":{"types":"./ai/providers/openai.d.ts","import":"./ai/providers/openai.js"},"./ai/providers/mistral":{"types":"./ai/providers/mistral.d.ts","import":"./ai/providers/mistral.js"},"./ai/providers/anthropic":{"types":"./ai/providers/anthropic.d.ts","import":"./ai/providers/anthropic.js"},"./ai/providers/openrouter":{"types":"./ai/providers/openrouter.d.ts","import":"./ai/providers/openrouter.js"},"./ai/providers/openai-compatible":{"types":"./ai/providers/openai-compatible.d.ts","import":"./ai/providers/openai-compatible.js"},"./ai/providers/openai-compatible-transcription":{"types":"./ai/providers/openai-compatible-transcription.d.ts","import":"./ai/providers/openai-compatible-transcription.js"}},"gitHead":"0d921f8179bb88173107eb9addf8f19df8d5bb22","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:51e1b19c-686e-4918-851c-842c23eef091"}},"repository":{"url":"git+https://github.com/k2b-dev/nessi.git","type":"git"},"_npmVersion":"11.5.1","description":"Minimal agent loop and provider adapters for nessi.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/nessi_0.12.0_1789052299274_0.44338698451332115","host":"s3://npm-registry-packages-npm-production"}},"0.12.1":{"_id":"@k2b/nessi@0.12.1","bugs":{"url":"https://github.com/k2b-dev/nessi/issues"},"dist":{"shasum":"9ccdaf6d0f2b7c65c97c9904bb054878f700f483","tarball":"https://registry.npmjs.org/@k2b/nessi/-/nessi-0.12.1.tgz","fileCount":73,"integrity":"sha512-simdZR0f9l3ExbWjwlKDjPeudhKOe4y1uO9onhc8819rRiORAzgdUhf1cuhterw9VKXwlGDv/aB70mwYghaEcQ==","signatures":[{"sig":"MEUCIH6/z5Xm63PN5YDgNf+GNx116kytUNpLqXPFwy9VyOvDAiEAz8AwzISODE7ko7T0RA3oYwZ45aHyERZvmEHgrc13+Xk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIH8Bkl5TYXqdq8dF/KO0E0kLpVc1oRG30Bldmf6RvOtGAiEAyIwRspILlb0iFzZXagaRbDf9IEj0Ka+0ygNBRAzfmqE="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@k2b%2fnessi@0.12.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":235589},"main":"index.js","name":"@k2b/nessi","type":"module","types":"index.d.ts","author":{"name":"Valentin Kolb"},"module":"index.js","exports":{".":{"types":"./index.d.ts","import":"./index.js"},"./ai":{"types":"./ai/index.d.ts","import":"./ai/index.js"},"./ai/providers/vllm":{"types":"./ai/providers/vllm.d.ts","import":"./ai/providers/vllm.js"},"./ai/providers/gemini":{"types":"./ai/providers/gemini.d.ts","import":"./ai/providers/gemini.js"},"./ai/providers/ollama":{"types":"./ai/providers/ollama.d.ts","import":"./ai/providers/ollama.js"},"./ai/providers/openai":{"types":"./ai/providers/openai.d.ts","import":"./ai/providers/openai.js"},"./ai/providers/mistral":{"types":"./ai/providers/mistral.d.ts","import":"./ai/providers/mistral.js"},"./ai/providers/anthropic":{"types":"./ai/providers/anthropic.d.ts","import":"./ai/providers/anthropic.js"},"./ai/providers/openrouter":{"types":"./ai/providers/openrouter.d.ts","import":"./ai/providers/openrouter.js"},"./ai/providers/openai-compatible":{"types":"./ai/providers/openai-compatible.d.ts","import":"./ai/providers/openai-compatible.js"},"./ai/providers/openai-compatible-transcription":{"types":"./ai/providers/openai-compatible-transcription.d.ts","import":"./ai/providers/openai-compatible-transcription.js"}},"gitHead":"65cc06018be865e74148bbddb18ccf3abc137552","license":"MIT","version":"0.12.1","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:51e1b19c-686e-4918-851c-842c23eef091"}},"homepage":"https://github.com/k2b-dev/nessi#readme","keywords":["ai","llm","agent","tools","streaming","openai","openrouter","ollama","anthropic","gemini","mistral"],"repository":{"url":"git+https://github.com/k2b-dev/nessi.git","type":"git"},"_npmVersion":"11.5.1","description":"Minimal agent loop and provider adapters for nessi.","directories":{},"maintainers":[{"name":"valentinkolb","email":"mail@valentin-kolb.com"}],"sideEffects":false,"_nodeVersion":"22.23.2","dependencies":{"zod":"^4.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nessi_0.12.1_1790093819854_0.07361339889023921"}}},"time":{"created":"2026-07-26T17:02:10.333Z","modified":"2026-09-22T16:17:00.344Z","0.10.0-rc.0":"2026-07-26T17:02:10.652Z","0.10.0":"2026-07-26T17:04:22.386Z","0.11.0":"2026-08-01T22:10:27.364Z","0.12.0":"2026-09-10T14:58:19.423Z","0.12.1":"2026-09-22T16:16:59.951Z"},"bugs":{"url":"https://github.com/k2b-dev/nessi/issues"},"author":{"name":"Valentin Kolb"},"license":"MIT","homepage":"https://github.com/k2b-dev/nessi#readme","keywords":["ai","llm","agent","tools","streaming","openai","openrouter","ollama","anthropic","gemini","mistral"],"repository":{"url":"git+https://github.com/k2b-dev/nessi.git","type":"git"},"description":"Minimal agent loop and provider adapters for nessi.","maintainers":[{"name":"valentinkolb","email":"mail@valentin-kolb.com"}],"readme":"# @k2b/nessi\n\nMinimal agent loop and provider adapters for TypeScript.\n\nUse the package root for the managed `nessi()` loop with tools, storage, and loop metadata. Use `@k2b/nessi/ai` when an app only needs provider calls through one normalized message and stream API.\n\n`@k2b/nessi` replaces the deprecated `@valentinkolb/nessi` package. Existing\nAPIs and subpaths are unchanged; replace the package scope in dependencies and\nimports.\n\n## Quick start\n\n```bash\nbun add @k2b/nessi\n```\n\n```ts\nimport { nessi, defineTool, memoryStore } from \"@k2b/nessi\";\nimport { ollama } from \"@k2b/nessi/ai\";\nimport { z } from \"zod\";\n\nconst weather = defineTool({\n  name: \"weather\",\n  description: \"Return a fake weather response\",\n  inputSchema: z.object({ city: z.string() }),\n}).server(async ({ city }) => {\n  return { city, forecast: \"sunny\" };\n});\n\nconst loop = nessi({\n  loopId: crypto.randomUUID(),\n  provider: ollama(\"llama3.1\", {\n    baseURL: \"http://localhost:11434\",\n  }),\n  systemPrompt: \"You are concise.\",\n  input: \"How is the weather in Berlin?\",\n  store: memoryStore(),\n  tools: [weather],\n  temperature: 0,\n  maxOutputTokens: 512,\n});\n\nconst textBlocks = new Set<string>();\n\nfor await (const event of loop) {\n  if (event.type === \"block_start\" && event.kind === \"text\") {\n    textBlocks.add(event.blockId);\n  }\n  if (event.type === \"block_delta\" && textBlocks.has(event.blockId)) {\n    process.stdout.write(event.delta);\n  }\n  if (event.type === \"issue\") {\n    console.error(event.issue.kind, event.issue.message);\n  }\n  if (event.type === \"loop_end\") {\n    console.log(event.loopId);\n    console.log(event.aggregate.usage);\n    console.log(event.aggregate.timing);\n  }\n}\n```\n\nEvery outbound event from one `nessi()` run carries the same `loopId`. Pass your own `loopId` to align events with a persisted request or UI response group, or let Nessi generate one when omitted.\n\n`turn_end` reports each internal provider turn. The final `loop_end` event includes `aggregate`, which groups assistant turns, executable tool calls, tool results, validation/execution errors, malformed or cancelled tool streams, summed usage, and timing for the complete logical loop. `aggregate.timing.totalElapsedMs` is model generation plus active tool execution; approval/client-tool waits are tracked separately as `aggregate.timing.actionWaitMs`. Helper exports such as `mergeUsage()`, `cloneLoopAggregate()`, and `mergeLoopAggregates()` are available from `@k2b/nessi`.\n\n## Dynamic tools\n\nPass a resolver when the active tools depend on application state:\n\n```ts\nconst loop = nessi({\n  provider,\n  systemPrompt,\n  input,\n  store,\n  tools: async () => toolRegistry.activeFor(userId),\n});\n```\n\nNessi resolves dynamic tools before every provider turn. One copied and\nvalidated snapshot supplies both the provider schemas and all tool execution\nfor that turn. Changes made during tool execution therefore become visible on\nthe following provider turn, while calls already emitted by the provider remain\nexecutable from their original snapshot. Duplicate names and resolver failures\nend the loop with a `runtime_error` issue.\n\nPending tool calls restored from history resolve one fresh snapshot before\nexecution because an in-memory snapshot cannot survive a restart. Keep the\nresolver side-effect free; persistence, discovery, authorization, and cleanup\nremain application-owned.\n\nServer tools receive the provider tool call ID through `ctx.callId`:\n\n```ts\nconst inspect = defineTool({\n  name: \"inspect\",\n  description: \"Inspect an item\",\n  inputSchema: z.object({ id: z.string() }),\n}).server(async ({ id }, ctx) => {\n  return inspectItem(id, { callId: ctx.callId, signal: ctx.signal });\n});\n```\n\n`nessi.structured()` accepts the same pattern for server tools:\n\n```ts\nconst result = await nessi.structured({\n  provider,\n  input: \"Resolve the current task.\",\n  output: taskSchema,\n  tools: () => toolRegistry.activeServerTools(),\n});\n```\n\nA structured tool resolver always selects `tool_loop` mode, even when a\nsnapshot is empty, because later turns may add tools. Nessi appends its internal\n`submit_result` tool to every snapshot. Client tools, approval tools, and a\nuser-defined `submit_result` remain unsupported. A static empty array keeps the\ndirect native/fallback structured-output path.\n\n## Historical tool results\n\nVerbose tool output can remain fully persisted without being sent to the model\nin every later loop. A tool may derive a compact historical representation once\nafter its output passes validation:\n\n```ts\nconst shell = defineTool({\n  name: \"shell\",\n  description: \"Run a shell command.\",\n  inputSchema: z.object({ command: z.string() }),\n  outputSchema: z.object({\n    exitCode: z.number(),\n    stdout: z.string(),\n    changedFiles: z.array(z.string()),\n  }),\n  toHistoricalResult: ({ output }) => ({\n    exitCode: output.exitCode,\n    changedFiles: output.changedFiles,\n    excerpt: output.stdout.slice(0, 500),\n  }),\n}).server(runShell);\n```\n\nNessi persists the full `result` and the optional `historicalResult` together.\nProvider calls in the originating loop, including resumes with the same\n`loopId`, receive the full result. Calls from a different loop receive the\nhistorical value instead. Stored messages, events, and loop aggregates remain\nfull and inspectable. Returning `undefined` skips the historical representation.\n\nIf derivation throws, Nessi stores the full successful result, emits a non-fatal\n`tool_historical_result_error` issue, and continues the loop. Legacy messages\nwithout `historicalResult` remain unchanged. `maxToolResultChars`, when set,\nruns after this selection as the final context-size boundary.\n\n## Steering\n\nUse `loop.steer()` when the process handling new input owns the running loop:\n\n```ts\nloop.steer(\"Skip deployment and only prepare the migration.\");\n```\n\nFor loops running in another worker or process, provide a `steering` callback\nthat reads pending messages from application-owned persistence:\n\n```ts\nconst loop = nessi({\n  provider,\n  systemPrompt,\n  store,\n  input,\n  tools,\n  steering: ({ loopId, signal }) => steeringQueue.takePending(loopId, { signal }),\n});\n```\n\nThe callback may return one message, an ordered array, or `undefined`. Nessi\nchecks it before provider calls and before a normal loop completion. Applied\nmessages are persisted as user messages and emit the same `steer_applied`\nevent as `loop.steer()`. The application owns persistence, claiming, and\ndelivery semantics; Nessi only controls when steering can affect the loop.\n\n## Structured output\n\nUse `nessi.structured()` when an app wants a schema-valid typed result instead\nof a streamed chat response:\n\n```ts\nimport { nessi } from \"@k2b/nessi\";\nimport { openrouter } from \"@k2b/nessi/ai\";\nimport { z } from \"zod\";\n\nconst result = await nessi.structured({\n  provider: openrouter(\"openai/gpt-4.1-mini\", {\n    apiKey: process.env.OPENROUTER_API_KEY,\n  }),\n  input: \"Extract a task card for: Ship the onboarding flow by Friday.\",\n  outputName: \"task_card\",\n  output: z.object({\n    title: z.string(),\n    due: z.string().nullable(),\n    priority: z.enum([\"low\", \"medium\", \"high\"]),\n  }),\n  temperature: 0,\n});\n\nconsole.log(result.output.title);\nconsole.log(result.structuredMeta);\nconsole.log(result.aggregate.usage);\n```\n\nFor providers and schemas that are safe for native structured output, Nessi\npasses a provider-specific `responseFormat`. Otherwise it falls back to schema\ninstructions and one repair attempt. `input` can be a string, content parts, or\na full user message, including image file parts when the provider supports\nimages.\n\n`nessi.structured()` can use server tools for bounded task work. It adds an\ninternal `submit_result` tool and returns after that tool receives a valid\nschema value. Client tools, approval tools, and interactive tool bridges remain\nthe job of the full `nessi()` loop.\n\n## Provider-only usage\n\n```ts\nimport { openrouter } from \"@k2b/nessi/ai\";\n\nconst provider = openrouter(\"openai/gpt-4.1-mini\", {\n  apiKey: process.env.OPENROUTER_API_KEY,\n});\n\nconst result = await provider.complete({\n  systemPrompt: \"Be concise.\",\n  messages: [\n    {\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Summarize this package.\" }],\n    },\n  ],\n});\n\nconsole.log(result.message.content);\n```\n\nProvider streams use the same block events as the root loop:\n\n```ts\nconst textBlocks = new Set<string>();\n\nfor await (const event of provider.stream({ messages })) {\n  if (event.type === \"block_start\" && event.kind === \"text\") {\n    textBlocks.add(event.blockId);\n  }\n  if (event.type === \"block_delta\" && textBlocks.has(event.blockId)) {\n    process.stdout.write(event.delta);\n  }\n  if (event.type === \"block_end\" && event.block.type === \"tool_call\") {\n    console.log(\"tool call\", event.block.name, event.block.args);\n  }\n  if (event.type === \"issue\") {\n    console.error(event.issue.kind, event.issue.message);\n  }\n}\n```\n\n## Audio transcription\n\nUse `openAICompatibleTranscription()` to upload an audio file to a service that\nimplements the OpenAI-compatible `/audio/transcriptions` endpoint. Configure\nthe service URL, API key and transcription model explicitly:\n\n```ts\nimport { openAICompatibleTranscription } from \"@k2b/nessi/ai\";\n\nconst speech = openAICompatibleTranscription(\"whisper-large-v3\", {\n  baseURL: \"https://api.scaleway.ai/v1\",\n  apiKey: process.env.SCW_SECRET_KEY,\n});\n\nconst result = await speech.transcribe({\n  file: Bun.file(\"./aufnahme.mp3\"),\n  filename: \"aufnahme.mp3\",\n  language: \"de\",\n  signal: AbortSignal.timeout(120_000),\n});\n\nconsole.log(result.text);\n```\n\nFor OpenAI, set `baseURL: \"https://api.openai.com/v1\"`, use your OpenAI key and\na supported transcription model such as `whisper-1`. Local compatible services\ncan omit `apiKey`. No environment variable is read automatically. Optional\n`headers` support gateways; `apiKey` overrides their Authorization header.\n\n`file` accepts a `Blob`, `File` or `Bun.file()`. Use `filename` to supply an\nextension for an unnamed Blob or override the filename. Automatically derived\nfilenames omit local directories. Omit `language`\nfor automatic detection. Optional `prompt` supplies vocabulary or context\nwhen supported by the model. File formats, size limits and optional parameter\nsupport depend on the service; Nessi does not convert or split audio.\n\nThe result is `{ text: string }`, including an empty string for an empty\ntranscript. HTTP, connection and malformed-response errors reject the promise.\nPass `signal` for cancellation or a timeout; cancellation preserves the signal's\nreason. There are no automatic retries or default timeout.\n\nTranscription uses its own `TranscriptionProvider` contract with `name`, `model`\nand `transcribe(request)`. Custom adapters can implement that interface for other\nprotocols. It is separate from the chat provider passed to `nessi()`; pass the\nresulting text into the agent when needed. Streaming transcription, timestamps\nand speaker identification are not exposed.\n\nThe example follows [Scaleway's audio API documentation](https://www.scaleway.com/en/docs/generative-apis/how-to/query-audio-models/).\nKeep API keys on the server when integrating a browser application.\n\n## Focused provider imports\n\n```ts\nimport { anthropic } from \"@k2b/nessi/ai/providers/anthropic\";\nimport { openai } from \"@k2b/nessi/ai/providers/openai\";\nimport { openAICompatibleTranscription } from \"@k2b/nessi/ai/providers/openai-compatible-transcription\";\n```\n\n## Features\n\n- Turn-based agent loop with canonical block streaming events\n- Stable `loopId` correlation across all events from one agent loop\n- `loop_start`, `turn_start`, `turn_end`, and `loop_end.aggregate` for logical response grouping\n- Local `loop.steer()` and optional `steering` callbacks for steering at safe loop boundaries\n- Loop timing metadata for wall time, generation time, active tool time, action wait time, and output tokens/second\n- `nessi.structured()` for typed schema-valid task results\n- Server tools and client tools\n- Tool approval flow and explicit `tool_action_request` events\n- Tool execution start/end events with per-tool `timeoutMs`\n- Optional per-tool historical result representations for bounded future context\n- Structured `issue` events for provider errors, timeouts, malformed tool streams, and tool execution failures\n- Pluggable session store\n- Optional history compaction\n- Standalone `compact()` loop with `loop_start`, `compaction_start`, `compaction_end`, `issue`, and `loop_end` events\n- Optional token-credit budgeting\n- Provider adapters with shared `complete()` and `stream()` APIs\n- Audio transcription through configurable OpenAI-compatible services\n- Native adapters for OpenAI, OpenRouter, vLLM, Ollama, Anthropic, Mistral, and Gemini\n\n## Package layout\n\n```txt\n@k2b/nessi\n  Agent loop, structured task helper, tools, stores, compaction, shared types\n\n@k2b/nessi/ai\n  Provider factories, provider types, complete(), stream(), responseFormat, transcribe()\n\n@k2b/nessi/ai/providers/*\n  Focused provider entrypoints\n```\n","readmeFilename":"README.md"}