@k2b/nessi 0.10.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/aggregates.d.ts +7 -0
  4. package/aggregates.js +115 -0
  5. package/ai/complete-from-stream.d.ts +2 -0
  6. package/ai/complete-from-stream.js +36 -0
  7. package/ai/index.d.ts +10 -0
  8. package/ai/index.js +9 -0
  9. package/ai/providers/anthropic.d.ts +13 -0
  10. package/ai/providers/anthropic.js +266 -0
  11. package/ai/providers/gemini.d.ts +12 -0
  12. package/ai/providers/gemini.js +192 -0
  13. package/ai/providers/mistral.d.ts +12 -0
  14. package/ai/providers/mistral.js +287 -0
  15. package/ai/providers/ollama.d.ts +10 -0
  16. package/ai/providers/ollama.js +241 -0
  17. package/ai/providers/openai-compatible.d.ts +2 -0
  18. package/ai/providers/openai-compatible.js +349 -0
  19. package/ai/providers/openai.d.ts +12 -0
  20. package/ai/providers/openai.js +22 -0
  21. package/ai/providers/openrouter.d.ts +13 -0
  22. package/ai/providers/openrouter.js +28 -0
  23. package/ai/providers/vllm.d.ts +11 -0
  24. package/ai/providers/vllm.js +22 -0
  25. package/ai/shared/errors.d.ts +15 -0
  26. package/ai/shared/errors.js +56 -0
  27. package/ai/shared/json.d.ts +3 -0
  28. package/ai/shared/json.js +15 -0
  29. package/ai/shared/messages.d.ts +15 -0
  30. package/ai/shared/messages.js +58 -0
  31. package/ai/shared/ndjson.d.ts +4 -0
  32. package/ai/shared/ndjson.js +60 -0
  33. package/ai/shared/sse.d.ts +15 -0
  34. package/ai/shared/sse.js +79 -0
  35. package/ai/shared/stream-helpers.d.ts +13 -0
  36. package/ai/shared/stream-helpers.js +105 -0
  37. package/ai/shared/tool-call-ids.d.ts +5 -0
  38. package/ai/shared/tool-call-ids.js +38 -0
  39. package/ai/shared/tool-stream-normalizer.d.ts +6 -0
  40. package/ai/shared/tool-stream-normalizer.js +271 -0
  41. package/ai/shared/tools.d.ts +29 -0
  42. package/ai/shared/tools.js +25 -0
  43. package/ai/shared/usage.d.ts +3 -0
  44. package/ai/shared/usage.js +5 -0
  45. package/ai/types.d.ts +252 -0
  46. package/ai/types.js +0 -0
  47. package/compact.d.ts +5 -0
  48. package/compact.js +108 -0
  49. package/index.d.ts +11 -0
  50. package/index.js +12 -0
  51. package/nessi.d.ts +2 -0
  52. package/nessi.js +1250 -0
  53. package/package.json +80 -0
  54. package/providers/ollama.d.ts +2 -0
  55. package/providers/ollama.js +1 -0
  56. package/providers/openai.d.ts +2 -0
  57. package/providers/openai.js +1 -0
  58. package/providers/openrouter.d.ts +2 -0
  59. package/providers/openrouter.js +1 -0
  60. package/stores.d.ts +11 -0
  61. package/stores.js +42 -0
  62. package/structured.d.ts +9 -0
  63. package/structured.js +413 -0
  64. package/tools.d.ts +25 -0
  65. package/tools.js +36 -0
  66. package/types.d.ts +290 -0
  67. package/types.js +3 -0
  68. package/utils.d.ts +15 -0
  69. package/utils.js +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ValentinKolb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # @k2b/nessi
2
+
3
+ Minimal agent loop and provider adapters for TypeScript.
4
+
5
+ Use 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.
6
+
7
+ `@k2b/nessi` replaces the deprecated `@valentinkolb/nessi` package. Existing
8
+ APIs and subpaths are unchanged; replace the package scope in dependencies and
9
+ imports.
10
+
11
+ ## Quick start
12
+
13
+ ```bash
14
+ bun add @k2b/nessi
15
+ ```
16
+
17
+ ```ts
18
+ import { nessi, defineTool, memoryStore } from "@k2b/nessi";
19
+ import { ollama } from "@k2b/nessi/ai";
20
+ import { z } from "zod";
21
+
22
+ const weather = defineTool({
23
+ name: "weather",
24
+ description: "Return a fake weather response",
25
+ inputSchema: z.object({ city: z.string() }),
26
+ }).server(async ({ city }) => {
27
+ return { city, forecast: "sunny" };
28
+ });
29
+
30
+ const loop = nessi({
31
+ loopId: crypto.randomUUID(),
32
+ provider: ollama("llama3.1", {
33
+ baseURL: "http://localhost:11434",
34
+ }),
35
+ systemPrompt: "You are concise.",
36
+ input: "How is the weather in Berlin?",
37
+ store: memoryStore(),
38
+ tools: [weather],
39
+ temperature: 0,
40
+ maxOutputTokens: 512,
41
+ });
42
+
43
+ const textBlocks = new Set<string>();
44
+
45
+ for await (const event of loop) {
46
+ if (event.type === "block_start" && event.kind === "text") {
47
+ textBlocks.add(event.blockId);
48
+ }
49
+ if (event.type === "block_delta" && textBlocks.has(event.blockId)) {
50
+ process.stdout.write(event.delta);
51
+ }
52
+ if (event.type === "issue") {
53
+ console.error(event.issue.kind, event.issue.message);
54
+ }
55
+ if (event.type === "loop_end") {
56
+ console.log(event.loopId);
57
+ console.log(event.aggregate.usage);
58
+ console.log(event.aggregate.timing);
59
+ }
60
+ }
61
+ ```
62
+
63
+ Every 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.
64
+
65
+ `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`.
66
+
67
+ ## Historical tool results
68
+
69
+ Verbose tool output can remain fully persisted without being sent to the model
70
+ in every later loop. A tool may derive a compact historical representation once
71
+ after its output passes validation:
72
+
73
+ ```ts
74
+ const shell = defineTool({
75
+ name: "shell",
76
+ description: "Run a shell command.",
77
+ inputSchema: z.object({ command: z.string() }),
78
+ outputSchema: z.object({
79
+ exitCode: z.number(),
80
+ stdout: z.string(),
81
+ changedFiles: z.array(z.string()),
82
+ }),
83
+ toHistoricalResult: ({ output }) => ({
84
+ exitCode: output.exitCode,
85
+ changedFiles: output.changedFiles,
86
+ excerpt: output.stdout.slice(0, 500),
87
+ }),
88
+ }).server(runShell);
89
+ ```
90
+
91
+ Nessi persists the full `result` and the optional `historicalResult` together.
92
+ Provider calls in the originating loop, including resumes with the same
93
+ `loopId`, receive the full result. Calls from a different loop receive the
94
+ historical value instead. Stored messages, events, and loop aggregates remain
95
+ full and inspectable. Returning `undefined` skips the historical representation.
96
+
97
+ If derivation throws, Nessi stores the full successful result, emits a non-fatal
98
+ `tool_historical_result_error` issue, and continues the loop. Legacy messages
99
+ without `historicalResult` remain unchanged. `maxToolResultChars`, when set,
100
+ runs after this selection as the final context-size boundary.
101
+
102
+ ## Steering
103
+
104
+ Use `loop.steer()` when the process handling new input owns the running loop:
105
+
106
+ ```ts
107
+ loop.steer("Skip deployment and only prepare the migration.");
108
+ ```
109
+
110
+ For loops running in another worker or process, provide a `steering` callback
111
+ that reads pending messages from application-owned persistence:
112
+
113
+ ```ts
114
+ const loop = nessi({
115
+ provider,
116
+ systemPrompt,
117
+ store,
118
+ input,
119
+ tools,
120
+ steering: ({ loopId, signal }) => steeringQueue.takePending(loopId, { signal }),
121
+ });
122
+ ```
123
+
124
+ The callback may return one message, an ordered array, or `undefined`. Nessi
125
+ checks it before provider calls and before a normal loop completion. Applied
126
+ messages are persisted as user messages and emit the same `steer_applied`
127
+ event as `loop.steer()`. The application owns persistence, claiming, and
128
+ delivery semantics; Nessi only controls when steering can affect the loop.
129
+
130
+ ## Structured output
131
+
132
+ Use `nessi.structured()` when an app wants a schema-valid typed result instead
133
+ of a streamed chat response:
134
+
135
+ ```ts
136
+ import { nessi } from "@k2b/nessi";
137
+ import { openrouter } from "@k2b/nessi/ai";
138
+ import { z } from "zod";
139
+
140
+ const result = await nessi.structured({
141
+ provider: openrouter("openai/gpt-4.1-mini", {
142
+ apiKey: process.env.OPENROUTER_API_KEY,
143
+ }),
144
+ input: "Extract a task card for: Ship the onboarding flow by Friday.",
145
+ outputName: "task_card",
146
+ output: z.object({
147
+ title: z.string(),
148
+ due: z.string().nullable(),
149
+ priority: z.enum(["low", "medium", "high"]),
150
+ }),
151
+ temperature: 0,
152
+ });
153
+
154
+ console.log(result.output.title);
155
+ console.log(result.structuredMeta);
156
+ console.log(result.aggregate.usage);
157
+ ```
158
+
159
+ For providers and schemas that are safe for native structured output, Nessi
160
+ passes a provider-specific `responseFormat`. Otherwise it falls back to schema
161
+ instructions and one repair attempt. `input` can be a string, content parts, or
162
+ a full user message, including image file parts when the provider supports
163
+ images.
164
+
165
+ `nessi.structured()` can use server tools for bounded task work. It adds an
166
+ internal `submit_result` tool and returns after that tool receives a valid
167
+ schema value. Client tools, approval tools, and interactive tool bridges remain
168
+ the job of the full `nessi()` loop.
169
+
170
+ ## Provider-only usage
171
+
172
+ ```ts
173
+ import { openrouter } from "@k2b/nessi/ai";
174
+
175
+ const provider = openrouter("openai/gpt-4.1-mini", {
176
+ apiKey: process.env.OPENROUTER_API_KEY,
177
+ });
178
+
179
+ const result = await provider.complete({
180
+ systemPrompt: "Be concise.",
181
+ messages: [
182
+ {
183
+ role: "user",
184
+ content: [{ type: "text", text: "Summarize this package." }],
185
+ },
186
+ ],
187
+ });
188
+
189
+ console.log(result.message.content);
190
+ ```
191
+
192
+ Provider streams use the same block events as the root loop:
193
+
194
+ ```ts
195
+ const textBlocks = new Set<string>();
196
+
197
+ for await (const event of provider.stream({ messages })) {
198
+ if (event.type === "block_start" && event.kind === "text") {
199
+ textBlocks.add(event.blockId);
200
+ }
201
+ if (event.type === "block_delta" && textBlocks.has(event.blockId)) {
202
+ process.stdout.write(event.delta);
203
+ }
204
+ if (event.type === "block_end" && event.block.type === "tool_call") {
205
+ console.log("tool call", event.block.name, event.block.args);
206
+ }
207
+ if (event.type === "issue") {
208
+ console.error(event.issue.kind, event.issue.message);
209
+ }
210
+ }
211
+ ```
212
+
213
+ ## Focused provider imports
214
+
215
+ ```ts
216
+ import { anthropic } from "@k2b/nessi/ai/providers/anthropic";
217
+ import { openai } from "@k2b/nessi/ai/providers/openai";
218
+ ```
219
+
220
+ ## Features
221
+
222
+ - Turn-based agent loop with canonical block streaming events
223
+ - Stable `loopId` correlation across all events from one agent loop
224
+ - `loop_start`, `turn_start`, `turn_end`, and `loop_end.aggregate` for logical response grouping
225
+ - Local `loop.steer()` and optional `steering` callbacks for steering at safe loop boundaries
226
+ - Loop timing metadata for wall time, generation time, active tool time, action wait time, and output tokens/second
227
+ - `nessi.structured()` for typed schema-valid task results
228
+ - Server tools and client tools
229
+ - Tool approval flow and explicit `tool_action_request` events
230
+ - Tool execution start/end events with per-tool `timeoutMs`
231
+ - Optional per-tool historical result representations for bounded future context
232
+ - Structured `issue` events for provider errors, timeouts, malformed tool streams, and tool execution failures
233
+ - Pluggable session store
234
+ - Optional history compaction
235
+ - Standalone `compact()` loop with `loop_start`, `compaction_start`, `compaction_end`, `issue`, and `loop_end` events
236
+ - Optional token-credit budgeting
237
+ - Provider adapters with shared `complete()` and `stream()` APIs
238
+ - Native adapters for OpenAI, OpenRouter, vLLM, Ollama, Anthropic, Mistral, and Gemini
239
+
240
+ ## Package layout
241
+
242
+ ```txt
243
+ @k2b/nessi
244
+ Agent loop, structured task helper, tools, stores, compaction, shared types
245
+
246
+ @k2b/nessi/ai
247
+ Provider factories, provider types, complete(), stream(), responseFormat
248
+
249
+ @k2b/nessi/ai/providers/*
250
+ Focused provider entrypoints
251
+ ```
@@ -0,0 +1,7 @@
1
+ import type { LoopAggregate, LoopIssueAggregate, LoopTimingAggregate, LoopTurnAggregate, Usage } from "./types.js";
2
+ export declare const cloneUsage: (usage: Usage | undefined) => Usage | undefined;
3
+ export declare const mergeUsage: (left: Usage | undefined, right: Usage | undefined) => Usage | undefined;
4
+ export declare const buildLoopTiming: (timing: Pick<LoopTimingAggregate, "wallMs" | "generationMs" | "toolExecutionMs" | "actionWaitMs">, usage: Usage | undefined) => LoopTimingAggregate;
5
+ export declare const aggregateFromTurns: (turns: LoopTurnAggregate[], loopIssues?: LoopIssueAggregate[], timing?: LoopTimingAggregate) => LoopAggregate;
6
+ export declare const cloneLoopAggregate: (aggregate: LoopAggregate) => LoopAggregate;
7
+ export declare const mergeLoopAggregates: (left: LoopAggregate | undefined, right: LoopAggregate | undefined) => LoopAggregate | undefined;
package/aggregates.js ADDED
@@ -0,0 +1,115 @@
1
+ // ============================================================================
2
+ // nessi - Loop aggregate helpers
3
+ // ============================================================================
4
+ export const cloneUsage = (usage) => usage ? { ...usage } : undefined;
5
+ export const mergeUsage = (left, right) => {
6
+ if (!right)
7
+ return cloneUsage(left);
8
+ const next = {
9
+ input: (left?.input ?? 0) + right.input,
10
+ output: (left?.output ?? 0) + right.output,
11
+ total: (left?.total ?? 0) + right.total,
12
+ };
13
+ if (left?.cacheRead !== undefined || right.cacheRead !== undefined) {
14
+ next.cacheRead = (left?.cacheRead ?? 0) + (right.cacheRead ?? 0);
15
+ }
16
+ if (left?.creditsUsed !== undefined || right.creditsUsed !== undefined) {
17
+ next.creditsUsed = (left?.creditsUsed ?? 0) + (right.creditsUsed ?? 0);
18
+ }
19
+ return next;
20
+ };
21
+ const cloneToolCall = (toolCall) => ({ ...toolCall });
22
+ const cloneToolIssue = (toolIssue) => ({ ...toolIssue });
23
+ const cloneIssue = (issue) => ({ ...issue });
24
+ const cloneTiming = (timing) => timing
25
+ ? {
26
+ ...timing,
27
+ totalElapsedMs: timing.totalElapsedMs ?? timing.generationMs + timing.toolExecutionMs,
28
+ }
29
+ : undefined;
30
+ const outputTokensPerSecond = (usage, generationMs) => {
31
+ if (!usage || usage.output <= 0 || generationMs <= 0)
32
+ return undefined;
33
+ return usage.output / (generationMs / 1000);
34
+ };
35
+ export const buildLoopTiming = (timing, usage) => {
36
+ const snapshot = {
37
+ ...timing,
38
+ totalElapsedMs: timing.generationMs + timing.toolExecutionMs,
39
+ };
40
+ const throughput = outputTokensPerSecond(usage, timing.generationMs);
41
+ if (throughput !== undefined)
42
+ snapshot.outputTokensPerSecond = throughput;
43
+ return snapshot;
44
+ };
45
+ const mergeTiming = (left, right, usage) => {
46
+ if (!left && !right)
47
+ return undefined;
48
+ const generationMs = (left?.generationMs ?? 0) + (right?.generationMs ?? 0);
49
+ return buildLoopTiming({
50
+ wallMs: (left?.wallMs ?? 0) + (right?.wallMs ?? 0),
51
+ generationMs,
52
+ toolExecutionMs: (left?.toolExecutionMs ?? 0) + (right?.toolExecutionMs ?? 0),
53
+ actionWaitMs: (left?.actionWaitMs ?? 0) + (right?.actionWaitMs ?? 0),
54
+ }, usage);
55
+ };
56
+ const toolIssuesFromAggregate = (aggregate) => (aggregate.toolIssues ?? aggregate.turns.flatMap((turn) => turn.toolIssues ?? [])).map(cloneToolIssue);
57
+ const issuesFromAggregate = (aggregate) => (aggregate.issues ?? aggregate.toolIssues ?? aggregate.turns.flatMap((turn) => turn.issues ?? turn.toolIssues ?? []))
58
+ .map(cloneIssue);
59
+ const cloneTurn = (turn) => ({
60
+ ...turn,
61
+ usage: cloneUsage(turn.usage),
62
+ toolCalls: turn.toolCalls.map(cloneToolCall),
63
+ ...(turn.toolIssues ? { toolIssues: turn.toolIssues.map(cloneToolIssue) } : {}),
64
+ ...(turn.issues ? { issues: turn.issues.map(cloneIssue) } : {}),
65
+ });
66
+ export const aggregateFromTurns = (turns, loopIssues = [], timing) => {
67
+ const clonedTurns = turns.map(cloneTurn);
68
+ const issues = loopIssues.length > 0
69
+ ? loopIssues.map(cloneIssue)
70
+ : clonedTurns.flatMap((turn) => turn.issues ?? turn.toolIssues ?? []);
71
+ const toolIssues = issues.filter((issue) => issue.kind === "malformed_tool_call" || issue.kind === "cancelled_tool_call");
72
+ const usage = clonedTurns.reduce((mergedUsage, turn) => mergeUsage(mergedUsage, turn.usage), undefined);
73
+ return {
74
+ turns: clonedTurns,
75
+ usage,
76
+ ...(timing ? { timing: cloneTiming(timing) } : {}),
77
+ issueCount: issues.length,
78
+ issues: issues.map(cloneIssue),
79
+ toolCallCount: clonedTurns.reduce((count, turn) => count + turn.toolCalls.length, 0),
80
+ toolErrorCount: clonedTurns.reduce((count, turn) => count + turn.toolCalls.filter((toolCall) => toolCall.isError).length, 0),
81
+ toolIssueCount: toolIssues.length,
82
+ toolMalformedCount: toolIssues.filter((issue) => issue.kind === "malformed_tool_call").length,
83
+ toolCancelledCount: toolIssues.filter((issue) => issue.kind === "cancelled_tool_call").length,
84
+ toolIssues: toolIssues.map(cloneToolIssue),
85
+ assistantMessageCount: clonedTurns.length,
86
+ };
87
+ };
88
+ export const cloneLoopAggregate = (aggregate) => {
89
+ const turns = aggregate.turns.map(cloneTurn);
90
+ const issues = issuesFromAggregate(aggregate);
91
+ const toolIssues = toolIssuesFromAggregate(aggregate);
92
+ return {
93
+ turns,
94
+ usage: cloneUsage(aggregate.usage),
95
+ ...(aggregate.timing ? { timing: cloneTiming(aggregate.timing) } : {}),
96
+ issueCount: aggregate.issueCount ?? issues.length,
97
+ issues,
98
+ toolCallCount: aggregate.toolCallCount,
99
+ toolErrorCount: aggregate.toolErrorCount,
100
+ toolIssueCount: aggregate.toolIssueCount ?? toolIssues.length,
101
+ toolMalformedCount: aggregate.toolMalformedCount ?? toolIssues.filter((issue) => issue.kind === "malformed_tool_call").length,
102
+ toolCancelledCount: aggregate.toolCancelledCount ?? toolIssues.filter((issue) => issue.kind === "cancelled_tool_call").length,
103
+ toolIssues,
104
+ assistantMessageCount: aggregate.assistantMessageCount,
105
+ };
106
+ };
107
+ export const mergeLoopAggregates = (left, right) => {
108
+ if (!left)
109
+ return right ? cloneLoopAggregate(right) : undefined;
110
+ if (!right)
111
+ return cloneLoopAggregate(left);
112
+ const merged = aggregateFromTurns([...left.turns, ...right.turns], [...issuesFromAggregate(left), ...issuesFromAggregate(right)]);
113
+ const timing = mergeTiming(left.timing, right.timing, merged.usage);
114
+ return timing ? { ...merged, timing } : merged;
115
+ };
@@ -0,0 +1,2 @@
1
+ import type { GenerateRequest, GenerateResult, Provider } from "./types.js";
2
+ export declare const completeFromStream: (provider: Pick<Provider, "model" | "stream">, request: GenerateRequest) => Promise<GenerateResult>;
@@ -0,0 +1,36 @@
1
+ import { appendAssistantContentBlock, buildAssistantMessageFromContent } from "./shared/messages.js";
2
+ export const completeFromStream = async (provider, request) => {
3
+ let usage;
4
+ let finishReason;
5
+ const content = [];
6
+ const toolCalls = [];
7
+ for await (const event of provider.stream(request)) {
8
+ switch (event.type) {
9
+ case "block_start":
10
+ case "block_delta":
11
+ break;
12
+ case "block_end":
13
+ if (event.block.type === "tool_call")
14
+ toolCalls.push(event.block);
15
+ appendAssistantContentBlock(content, event.block);
16
+ break;
17
+ case "issue":
18
+ if (event.issue.kind === "provider_error")
19
+ throw new Error(event.issue.message);
20
+ if (event.issue.kind === "timeout" && event.issue.scope !== "tool")
21
+ throw new Error(event.issue.message);
22
+ break;
23
+ case "usage":
24
+ usage = event.usage;
25
+ finishReason = event.finishReason ?? finishReason;
26
+ break;
27
+ }
28
+ }
29
+ finishReason ??= toolCalls.length > 0 ? "tool_use" : "stop";
30
+ return {
31
+ message: buildAssistantMessageFromContent(provider.model, content, usage, finishReason),
32
+ usage,
33
+ finishReason,
34
+ providerMeta: { model: provider.model },
35
+ };
36
+ };
package/ai/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export { completeFromStream } from "./complete-from-stream.js";
2
+ export { openAICompatible } from "./providers/openai-compatible.js";
3
+ export { openai } from "./providers/openai.js";
4
+ export { openrouter } from "./providers/openrouter.js";
5
+ export { vllm } from "./providers/vllm.js";
6
+ export { ollama } from "./providers/ollama.js";
7
+ export { anthropic } from "./providers/anthropic.js";
8
+ export { mistral } from "./providers/mistral.js";
9
+ export { gemini } from "./providers/gemini.js";
10
+ export type { AssistantBlockKind, AssistantContentBlock, AssistantMessage, AssistantStopReason, BlockDeltaEvent, BlockEndEvent, BlockStartEvent, ContentPart, GenerateRequest, GenerateResult, HistoricalToolResult, InputFilePart, JsonSchemaObject, Message, NessiIssue, Provider, ProviderCapabilities, ProviderFamily, ProviderIssue, ProviderTimeouts, RuntimeIssue, ResponseFormat, StreamEvent, TextBlock, ThinkingBlock, ToolCallBlock, ToolExecutionIssue, ToolHistoricalResultIssue, ToolResultMessage, ToolStreamIssue, ToolStreamIssueKind, ToolStreamIssueReason, ToolSpec, TimeoutIssue, Usage, UserMessage, OpenAICompat, OpenAICompatibleConfig, } from "./types.js";
package/ai/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { completeFromStream } from "./complete-from-stream.js";
2
+ export { openAICompatible } from "./providers/openai-compatible.js";
3
+ export { openai } from "./providers/openai.js";
4
+ export { openrouter } from "./providers/openrouter.js";
5
+ export { vllm } from "./providers/vllm.js";
6
+ export { ollama } from "./providers/ollama.js";
7
+ export { anthropic } from "./providers/anthropic.js";
8
+ export { mistral } from "./providers/mistral.js";
9
+ export { gemini } from "./providers/gemini.js";
@@ -0,0 +1,13 @@
1
+ import type { Provider, ProviderTimeouts } from "../types.js";
2
+ export type AnthropicOptions = {
3
+ apiKey?: string;
4
+ baseURL?: string;
5
+ apiVersion?: string;
6
+ contextWindow?: number;
7
+ temperature?: number;
8
+ maxOutputTokens?: number;
9
+ creditsPerInputToken?: number;
10
+ creditsPerOutputToken?: number;
11
+ timeouts?: ProviderTimeouts;
12
+ };
13
+ export declare const anthropic: (model: string, options?: AnthropicOptions) => Provider;