@byfriends/kosong 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +11 -0
- package/dist/anthropic-Dm_GqFgS.d.mts +69 -0
- package/dist/capability-registry-CMBuEYcf.mjs +161 -0
- package/dist/chat-completions-stream-BuMu_xr9.mjs +62 -0
- package/dist/errors-DweKbIOf.d.mts +42 -0
- package/dist/errors-WFxxzL1B.mjs +80 -0
- package/dist/google-genai-hX0X6CF3.d.mts +98 -0
- package/dist/index.d.mts +161 -0
- package/dist/index.mjs +287 -0
- package/dist/openai-common-08qin3UI.mjs +278 -0
- package/dist/openai-common-B6cK2ig3.d.mts +105 -0
- package/dist/openai-compat-CMrIk-ib.d.mts +132 -0
- package/dist/openai-compat-CWbwO4b7.mjs +801 -0
- package/dist/openai-legacy-B6CVfLlr.d.mts +71 -0
- package/dist/openai-responses-BxOwxtd3.d.mts +65 -0
- package/dist/provider-DiJKWMsQ.d.mts +371 -0
- package/dist/providers/anthropic.d.mts +2 -0
- package/dist/providers/anthropic.mjs +720 -0
- package/dist/providers/google-genai.d.mts +2 -0
- package/dist/providers/google-genai.mjs +562 -0
- package/dist/providers/openai-common.d.mts +2 -0
- package/dist/providers/openai-common.mjs +2 -0
- package/dist/providers/openai-compat.d.mts +2 -0
- package/dist/providers/openai-compat.mjs +2 -0
- package/dist/providers/openai-legacy.d.mts +2 -0
- package/dist/providers/openai-legacy.mjs +248 -0
- package/dist/providers/openai-responses.d.mts +2 -0
- package/dist/providers/openai-responses.mjs +623 -0
- package/dist/request-auth-DCWSyCKI.mjs +63 -0
- package/package.json +89 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { A as createUserMessage, C as TextPart, D as VideoURLPart, E as ToolCallPart, F as mergeInPlace, M as isContentPart, N as isToolCall, O as createAssistantMessage, P as isToolCallPart, S as StreamedMessagePart, T as ToolCall, _ as AudioURLPart, a as StreamedMessage, b as Message, c as TokenUsage, d as grandTotal, f as inputTotal, g as isUnknownCapability, h as UNKNOWN_CAPABILITY, i as ProviderRequestAuth, j as extractText, k as createToolMessage, l as addUsage, m as ModelCapability, n as FinishReason, o as ThinkingEffort, p as Tool, r as GenerateOptions, s as VideoUploadInput, t as ChatProvider, u as emptyUsage, v as ContentPart, w as ThinkPart, x as Role, y as ImageURLPart } from "./provider-DiJKWMsQ.mjs";
|
|
2
|
+
import { a as APITimeoutError, i as APIStatusError, n as APIContextOverflowError, o as ChatProviderError, r as APIEmptyResponseError, t as APIConnectionError } from "./errors-DweKbIOf.mjs";
|
|
3
|
+
import { n as AnthropicOptions } from "./anthropic-Dm_GqFgS.mjs";
|
|
4
|
+
import { r as GoogleGenAIOptions } from "./google-genai-hX0X6CF3.mjs";
|
|
5
|
+
import { i as OpenAICompatOptions } from "./openai-compat-CMrIk-ib.mjs";
|
|
6
|
+
import { r as OpenAILegacyOptions } from "./openai-legacy-B6CVfLlr.mjs";
|
|
7
|
+
import { r as OpenAIResponsesOptions } from "./openai-responses-BxOwxtd3.mjs";
|
|
8
|
+
|
|
9
|
+
//#region src/providers/index.d.ts
|
|
10
|
+
type ProviderConfig = ({
|
|
11
|
+
type: 'anthropic';
|
|
12
|
+
} & AnthropicOptions) | ({
|
|
13
|
+
type: 'openai';
|
|
14
|
+
} & OpenAILegacyOptions) | ({
|
|
15
|
+
type: 'openai-compat';
|
|
16
|
+
} & OpenAICompatOptions) | ({
|
|
17
|
+
type: 'google-genai';
|
|
18
|
+
} & GoogleGenAIOptions) | ({
|
|
19
|
+
type: 'openai_responses';
|
|
20
|
+
} & OpenAIResponsesOptions) | ({
|
|
21
|
+
type: 'vertexai';
|
|
22
|
+
} & GoogleGenAIOptions);
|
|
23
|
+
type ProviderType = ProviderConfig['type'];
|
|
24
|
+
declare function createProvider(config: ProviderConfig): ChatProvider;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/catalog.d.ts
|
|
27
|
+
/**
|
|
28
|
+
* models.dev-style catalog: a public map of provider/model metadata. Callers
|
|
29
|
+
* consume a snapshot of this shape to populate provider + model configuration
|
|
30
|
+
* without hand-writing context windows or capabilities.
|
|
31
|
+
*/
|
|
32
|
+
interface CatalogModelEntry {
|
|
33
|
+
readonly id?: string;
|
|
34
|
+
readonly name?: string;
|
|
35
|
+
readonly family?: string;
|
|
36
|
+
readonly limit?: {
|
|
37
|
+
readonly context?: number;
|
|
38
|
+
readonly output?: number;
|
|
39
|
+
};
|
|
40
|
+
readonly tool_call?: boolean;
|
|
41
|
+
readonly reasoning?: boolean;
|
|
42
|
+
readonly interleaved?: boolean | {
|
|
43
|
+
readonly field?: string;
|
|
44
|
+
};
|
|
45
|
+
readonly modalities?: {
|
|
46
|
+
readonly input?: readonly string[];
|
|
47
|
+
readonly output?: readonly string[];
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
interface CatalogProviderEntry {
|
|
51
|
+
readonly id?: string;
|
|
52
|
+
readonly name?: string;
|
|
53
|
+
/** Base URL for the provider; may be empty (some SDKs hardcode it). */
|
|
54
|
+
readonly api?: string;
|
|
55
|
+
/** Env var names carrying credentials — surfaced as a hint by callers. */
|
|
56
|
+
readonly env?: readonly string[];
|
|
57
|
+
/** models.dev SDK package id; used to infer the wire type when `type` is absent. */
|
|
58
|
+
readonly npm?: string;
|
|
59
|
+
/** Explicit wire type extension; inferred from `npm`/`id` when absent. */
|
|
60
|
+
readonly type?: string;
|
|
61
|
+
readonly models?: Record<string, CatalogModelEntry>;
|
|
62
|
+
}
|
|
63
|
+
/** Top-level catalog: `{ [providerId]: ProviderEntry }` (e.g. models.dev/api.json). */
|
|
64
|
+
type Catalog = Record<string, CatalogProviderEntry>;
|
|
65
|
+
/** A normalized catalog model: identity plus its {@link ModelCapability}. */
|
|
66
|
+
interface CatalogModel {
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly name?: string;
|
|
69
|
+
readonly maxOutputSize?: number;
|
|
70
|
+
readonly reasoningKey?: string;
|
|
71
|
+
readonly capability: ModelCapability;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolves a catalog provider entry to a supported wire type. Honors an
|
|
75
|
+
* explicit `type`, otherwise infers from `npm`/`id`. Unknown providers return
|
|
76
|
+
* `undefined` so callers can omit them instead of writing an invalid config.
|
|
77
|
+
*/
|
|
78
|
+
declare function inferWireType(entry: CatalogProviderEntry): ProviderType | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* Resolves the base URL to store for a catalog provider, adapting the catalog's
|
|
81
|
+
* `api` to the wire's SDK convention.
|
|
82
|
+
*
|
|
83
|
+
* models.dev `api` URLs are written for the SDK named in `npm` (e.g.
|
|
84
|
+
* `@ai-sdk/anthropic`), whose base already includes the `/v1` version segment.
|
|
85
|
+
* We route the `anthropic` wire through the official `@anthropic-ai/sdk`, which
|
|
86
|
+
* appends `/v1/messages` itself — so a catalog `api` ending in `/v1` would POST
|
|
87
|
+
* to `/v1/v1/messages` (404). Strip the trailing `/v1` for anthropic. OpenAI
|
|
88
|
+
* family SDKs append `/chat/completions` to a `/v1` base, so those pass through.
|
|
89
|
+
*/
|
|
90
|
+
declare function catalogBaseUrl(entry: CatalogProviderEntry, wire: ProviderType): string | undefined;
|
|
91
|
+
/** Normalizes one catalog model entry into a {@link CatalogModel}; skips invalid entries. */
|
|
92
|
+
declare function catalogModelToCapability(model: CatalogModelEntry): CatalogModel | undefined;
|
|
93
|
+
/** Extracts the valid, normalized models from a catalog provider entry. */
|
|
94
|
+
declare function catalogProviderModels(entry: CatalogProviderEntry): CatalogModel[];
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/generate.d.ts
|
|
97
|
+
/**
|
|
98
|
+
* The result of a single {@link generate} call.
|
|
99
|
+
*
|
|
100
|
+
* Contains the fully-assembled assistant {@link message}, an optional
|
|
101
|
+
* provider-assigned {@link id}, and token {@link usage} statistics.
|
|
102
|
+
*/
|
|
103
|
+
interface GenerateResult {
|
|
104
|
+
/** Provider-assigned response identifier, or `null` if unavailable. */
|
|
105
|
+
readonly id: string | null;
|
|
106
|
+
/** The fully-assembled assistant message with merged content parts and tool calls. */
|
|
107
|
+
readonly message: Message;
|
|
108
|
+
/** Token usage for this generation, or `null` if not reported. */
|
|
109
|
+
readonly usage: TokenUsage | null;
|
|
110
|
+
/**
|
|
111
|
+
* Normalized finish reason reported by the provider, or `null` if no
|
|
112
|
+
* finish_reason was emitted (for example, the stream was interrupted
|
|
113
|
+
* before the final event).
|
|
114
|
+
*/
|
|
115
|
+
readonly finishReason: FinishReason | null;
|
|
116
|
+
/**
|
|
117
|
+
* Raw provider-specific finish_reason string preserved verbatim.
|
|
118
|
+
* `null` if the provider did not emit one.
|
|
119
|
+
*/
|
|
120
|
+
readonly rawFinishReason: string | null;
|
|
121
|
+
}
|
|
122
|
+
interface GenerateCallbacks {
|
|
123
|
+
onMessagePart?: (part: StreamedMessagePart) => void | Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Fires once per fully-assembled tool call after the stream drains, in the
|
|
126
|
+
* order tool calls appear in the final assistant message.
|
|
127
|
+
*
|
|
128
|
+
* Tool calls are deliberately deferred until after the stream completes:
|
|
129
|
+
* parallel-tool-call streams may interleave argument deltas across calls
|
|
130
|
+
* (e.g. tc0-header → tc1-header → tc0-args → tc1-args), so firing mid-stream
|
|
131
|
+
* would dispatch a tool with half-parsed arguments and trigger toolParseError.
|
|
132
|
+
*/
|
|
133
|
+
onToolCall?: (toolCall: ToolCall) => void | Promise<void>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Generate one assistant message by streaming from the given provider.
|
|
137
|
+
*
|
|
138
|
+
* Parts of the message are streamed and merged: consecutive compatible parts
|
|
139
|
+
* (e.g. TextPart + TextPart, ToolCall + ToolCallPart) are merged in-place so
|
|
140
|
+
* the returned message always contains fully-assembled parts.
|
|
141
|
+
*
|
|
142
|
+
* **Tool call completion** is inferred from merge boundaries (a non-merging
|
|
143
|
+
* next part flushes the pending tool call into `message.toolCalls`) and from
|
|
144
|
+
* stream end. Provider adapters translate native "done" signals into this
|
|
145
|
+
* unified form; the generate loop never sees a separate done event.
|
|
146
|
+
*
|
|
147
|
+
* @param provider - The chat provider to generate from.
|
|
148
|
+
* @param systemPrompt - System-level instruction prepended to the request.
|
|
149
|
+
* @param tools - Tool definitions the model may invoke.
|
|
150
|
+
* @param history - The conversation history sent as context.
|
|
151
|
+
* @param callbacks - Optional streaming callbacks.
|
|
152
|
+
* @param options - Optional per-call settings (e.g. an {@link AbortSignal}).
|
|
153
|
+
*
|
|
154
|
+
* @throws {DOMException} with name `"AbortError"` when `options.signal` is
|
|
155
|
+
* aborted before or during streaming.
|
|
156
|
+
* @throws {APIEmptyResponseError} when the response contains no content and
|
|
157
|
+
* no tool calls, or only thinking content without any text or tool calls.
|
|
158
|
+
*/
|
|
159
|
+
declare function generate(provider: ChatProvider, systemPrompt: string, tools: Tool[], history: Message[], callbacks?: GenerateCallbacks, options?: GenerateOptions): Promise<GenerateResult>;
|
|
160
|
+
//#endregion
|
|
161
|
+
export { APIConnectionError, APIContextOverflowError, APIEmptyResponseError, APIStatusError, APITimeoutError, type AudioURLPart, type Catalog, type CatalogModel, type CatalogModelEntry, type CatalogProviderEntry, ChatProvider, ChatProviderError, type ContentPart, FinishReason, type GenerateCallbacks, GenerateOptions, type GenerateResult, type ImageURLPart, type Message, type ModelCapability, type ProviderConfig, ProviderRequestAuth, type ProviderType, type Role, StreamedMessage, type StreamedMessagePart, type TextPart, type ThinkPart, ThinkingEffort, type TokenUsage, type Tool, type ToolCall, type ToolCallPart, UNKNOWN_CAPABILITY, type VideoURLPart, VideoUploadInput, addUsage, catalogBaseUrl, catalogModelToCapability, catalogProviderModels, createAssistantMessage, createProvider, createToolMessage, createUserMessage, emptyUsage, extractText, generate, grandTotal, inferWireType, inputTotal, isContentPart, isToolCall, isToolCallPart, isUnknownCapability, mergeInPlace };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { _ as mergeInPlace, d as createToolMessage, f as createUserMessage, g as isToolCallPart, h as isToolCall, m as isContentPart, p as extractText, u as createAssistantMessage } from "./openai-common-08qin3UI.mjs";
|
|
2
|
+
import { a as APITimeoutError, i as APIStatusError, n as APIContextOverflowError, o as ChatProviderError, r as APIEmptyResponseError, t as APIConnectionError } from "./errors-WFxxzL1B.mjs";
|
|
3
|
+
import { a as isUnknownCapability, i as UNKNOWN_CAPABILITY } from "./request-auth-DCWSyCKI.mjs";
|
|
4
|
+
import { AnthropicChatProvider } from "./providers/anthropic.mjs";
|
|
5
|
+
import { GoogleGenAIChatProvider } from "./providers/google-genai.mjs";
|
|
6
|
+
import { t as OpenAICompatChatProvider } from "./openai-compat-CWbwO4b7.mjs";
|
|
7
|
+
import { OpenAILegacyChatProvider } from "./providers/openai-legacy.mjs";
|
|
8
|
+
import { OpenAIResponsesChatProvider } from "./providers/openai-responses.mjs";
|
|
9
|
+
//#region src/providers/index.ts
|
|
10
|
+
function createProvider(config) {
|
|
11
|
+
switch (config.type) {
|
|
12
|
+
case "anthropic": return new AnthropicChatProvider(config);
|
|
13
|
+
case "openai": return new OpenAILegacyChatProvider(config);
|
|
14
|
+
case "openai-compat": return new OpenAICompatChatProvider(config);
|
|
15
|
+
case "google-genai": return new GoogleGenAIChatProvider(config);
|
|
16
|
+
case "openai_responses": return new OpenAIResponsesChatProvider(config);
|
|
17
|
+
case "vertexai": return new GoogleGenAIChatProvider(config);
|
|
18
|
+
default: throw new Error(`Unknown provider type: ${String(config)}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/catalog.ts
|
|
23
|
+
const KNOWN_WIRE_TYPES = [
|
|
24
|
+
"anthropic",
|
|
25
|
+
"openai",
|
|
26
|
+
"openai-compat",
|
|
27
|
+
"google-genai",
|
|
28
|
+
"openai_responses",
|
|
29
|
+
"vertexai"
|
|
30
|
+
];
|
|
31
|
+
function isWireType(value) {
|
|
32
|
+
return typeof value === "string" && KNOWN_WIRE_TYPES.includes(value);
|
|
33
|
+
}
|
|
34
|
+
function hasEmbeddingMarker(value) {
|
|
35
|
+
if (value === void 0) return false;
|
|
36
|
+
const lower = value.toLowerCase();
|
|
37
|
+
return lower.includes("embedding") || /(?:^|[-_/])embed(?:$|[-_/])/.test(lower);
|
|
38
|
+
}
|
|
39
|
+
function isUsableChatModel(model) {
|
|
40
|
+
const outputModalities = model.modalities?.output;
|
|
41
|
+
if (outputModalities !== void 0 && !outputModalities.includes("text")) return false;
|
|
42
|
+
return !hasEmbeddingMarker(model.family) && !hasEmbeddingMarker(model.id) && !hasEmbeddingMarker(model.name);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolves a catalog provider entry to a supported wire type. Honors an
|
|
46
|
+
* explicit `type`, otherwise infers from `npm`/`id`. Unknown providers return
|
|
47
|
+
* `undefined` so callers can omit them instead of writing an invalid config.
|
|
48
|
+
*/
|
|
49
|
+
function inferWireType(entry) {
|
|
50
|
+
if (isWireType(entry.type)) return entry.type;
|
|
51
|
+
const npm = (entry.npm ?? "").toLowerCase();
|
|
52
|
+
const id = (entry.id ?? "").toLowerCase();
|
|
53
|
+
if (npm.includes("anthropic") || id.includes("anthropic") || id.includes("claude")) return "anthropic";
|
|
54
|
+
if (id.includes("vertex")) return "vertexai";
|
|
55
|
+
if (npm.includes("google") || id.includes("google") || id.includes("gemini")) return "google-genai";
|
|
56
|
+
if (npm.includes("openai") || id.includes("openai")) return "openai";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolves the base URL to store for a catalog provider, adapting the catalog's
|
|
60
|
+
* `api` to the wire's SDK convention.
|
|
61
|
+
*
|
|
62
|
+
* models.dev `api` URLs are written for the SDK named in `npm` (e.g.
|
|
63
|
+
* `@ai-sdk/anthropic`), whose base already includes the `/v1` version segment.
|
|
64
|
+
* We route the `anthropic` wire through the official `@anthropic-ai/sdk`, which
|
|
65
|
+
* appends `/v1/messages` itself — so a catalog `api` ending in `/v1` would POST
|
|
66
|
+
* to `/v1/v1/messages` (404). Strip the trailing `/v1` for anthropic. OpenAI
|
|
67
|
+
* family SDKs append `/chat/completions` to a `/v1` base, so those pass through.
|
|
68
|
+
*/
|
|
69
|
+
function catalogBaseUrl(entry, wire) {
|
|
70
|
+
const api = entry.api;
|
|
71
|
+
if (typeof api !== "string" || api.length === 0) return void 0;
|
|
72
|
+
if (wire === "anthropic") return api.replace(/\/v1\/?$/, "");
|
|
73
|
+
return api;
|
|
74
|
+
}
|
|
75
|
+
/** Normalizes one catalog model entry into a {@link CatalogModel}; skips invalid entries. */
|
|
76
|
+
function catalogModelToCapability(model) {
|
|
77
|
+
if (typeof model.id !== "string" || model.id.length === 0) return void 0;
|
|
78
|
+
const context = model.limit?.context;
|
|
79
|
+
if (typeof context !== "number" || !Number.isInteger(context) || context <= 0) return void 0;
|
|
80
|
+
if (!isUsableChatModel(model)) return void 0;
|
|
81
|
+
const inputs = model.modalities?.input ?? [];
|
|
82
|
+
const output = model.limit?.output;
|
|
83
|
+
return {
|
|
84
|
+
id: model.id,
|
|
85
|
+
name: typeof model.name === "string" && model.name.length > 0 ? model.name : void 0,
|
|
86
|
+
maxOutputSize: typeof output === "number" && output > 0 ? output : void 0,
|
|
87
|
+
reasoningKey: catalogReasoningKey(model.interleaved),
|
|
88
|
+
capability: {
|
|
89
|
+
image_in: inputs.includes("image"),
|
|
90
|
+
video_in: inputs.includes("video"),
|
|
91
|
+
audio_in: inputs.includes("audio"),
|
|
92
|
+
thinking: Boolean(model.reasoning),
|
|
93
|
+
tool_use: model.tool_call ?? true,
|
|
94
|
+
max_context_tokens: context
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function catalogReasoningKey(interleaved) {
|
|
99
|
+
if (interleaved === true) return "reasoning_content";
|
|
100
|
+
if (typeof interleaved !== "object" || interleaved === null) return void 0;
|
|
101
|
+
const field = interleaved.field?.trim();
|
|
102
|
+
return field !== void 0 && field.length > 0 ? field : void 0;
|
|
103
|
+
}
|
|
104
|
+
/** Extracts the valid, normalized models from a catalog provider entry. */
|
|
105
|
+
function catalogProviderModels(entry) {
|
|
106
|
+
const models = entry.models ?? {};
|
|
107
|
+
return Object.values(models).map((model) => catalogModelToCapability(model)).filter((model) => model !== void 0);
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/generate.ts
|
|
111
|
+
/**
|
|
112
|
+
* Generate one assistant message by streaming from the given provider.
|
|
113
|
+
*
|
|
114
|
+
* Parts of the message are streamed and merged: consecutive compatible parts
|
|
115
|
+
* (e.g. TextPart + TextPart, ToolCall + ToolCallPart) are merged in-place so
|
|
116
|
+
* the returned message always contains fully-assembled parts.
|
|
117
|
+
*
|
|
118
|
+
* **Tool call completion** is inferred from merge boundaries (a non-merging
|
|
119
|
+
* next part flushes the pending tool call into `message.toolCalls`) and from
|
|
120
|
+
* stream end. Provider adapters translate native "done" signals into this
|
|
121
|
+
* unified form; the generate loop never sees a separate done event.
|
|
122
|
+
*
|
|
123
|
+
* @param provider - The chat provider to generate from.
|
|
124
|
+
* @param systemPrompt - System-level instruction prepended to the request.
|
|
125
|
+
* @param tools - Tool definitions the model may invoke.
|
|
126
|
+
* @param history - The conversation history sent as context.
|
|
127
|
+
* @param callbacks - Optional streaming callbacks.
|
|
128
|
+
* @param options - Optional per-call settings (e.g. an {@link AbortSignal}).
|
|
129
|
+
*
|
|
130
|
+
* @throws {DOMException} with name `"AbortError"` when `options.signal` is
|
|
131
|
+
* aborted before or during streaming.
|
|
132
|
+
* @throws {APIEmptyResponseError} when the response contains no content and
|
|
133
|
+
* no tool calls, or only thinking content without any text or tool calls.
|
|
134
|
+
*/
|
|
135
|
+
async function generate(provider, systemPrompt, tools, history, callbacks, options) {
|
|
136
|
+
const message = {
|
|
137
|
+
role: "assistant",
|
|
138
|
+
content: [],
|
|
139
|
+
toolCalls: []
|
|
140
|
+
};
|
|
141
|
+
let pendingPart = null;
|
|
142
|
+
const toolCallIndexMap = /* @__PURE__ */ new Map();
|
|
143
|
+
if (options?.signal?.aborted) throwAbortError();
|
|
144
|
+
const stream = await provider.generate(systemPrompt, tools, history, options);
|
|
145
|
+
await throwIfAborted(options?.signal, stream);
|
|
146
|
+
for await (const part of stream) {
|
|
147
|
+
await throwIfAborted(options?.signal, stream);
|
|
148
|
+
if (callbacks?.onMessagePart !== void 0) {
|
|
149
|
+
await callbacks.onMessagePart(deepCopyPart(part));
|
|
150
|
+
await throwIfAborted(options?.signal, stream);
|
|
151
|
+
}
|
|
152
|
+
if (isToolCallPart(part) && part.index !== void 0 && !isPendingToolCallAtIndex(pendingPart, part.index)) {
|
|
153
|
+
const arrayIdx = toolCallIndexMap.get(part.index);
|
|
154
|
+
if (arrayIdx !== void 0) {
|
|
155
|
+
const target = message.toolCalls[arrayIdx];
|
|
156
|
+
if (target !== void 0 && part.argumentsPart !== null) target.arguments = target.arguments === null ? part.argumentsPart : target.arguments + part.argumentsPart;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (pendingPart === null) pendingPart = part;
|
|
161
|
+
else if (!mergeInPlace(pendingPart, part)) {
|
|
162
|
+
flushPart(message, pendingPart, toolCallIndexMap);
|
|
163
|
+
pendingPart = part;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
await throwIfAborted(options?.signal, stream);
|
|
167
|
+
if (pendingPart !== null) flushPart(message, pendingPart, toolCallIndexMap);
|
|
168
|
+
if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError(`The API returned an empty response (no content, no tool calls). Provider: ${provider.name}, model: ${provider.modelName}`);
|
|
169
|
+
const hasThink = message.content.some((p) => p.type === "think");
|
|
170
|
+
const hasText = message.content.some((p) => p.type === "text" && p.text.trim().length > 0);
|
|
171
|
+
const hasToolCalls = message.toolCalls.length > 0;
|
|
172
|
+
if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError(`The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning. Provider: ${provider.name}, model: ${provider.modelName}`);
|
|
173
|
+
if (callbacks?.onToolCall !== void 0) for (const toolCall of message.toolCalls) {
|
|
174
|
+
await throwIfAborted(options?.signal, stream);
|
|
175
|
+
await callbacks.onToolCall(toolCall);
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
id: stream.id,
|
|
179
|
+
message,
|
|
180
|
+
usage: stream.usage,
|
|
181
|
+
finishReason: stream.finishReason,
|
|
182
|
+
rawFinishReason: stream.rawFinishReason
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function throwAbortError() {
|
|
186
|
+
throw new DOMException("The operation was aborted.", "AbortError");
|
|
187
|
+
}
|
|
188
|
+
async function cancelStream(stream) {
|
|
189
|
+
const cancelable = stream;
|
|
190
|
+
try {
|
|
191
|
+
await cancelable.cancel?.();
|
|
192
|
+
} catch {}
|
|
193
|
+
try {
|
|
194
|
+
await cancelable.return?.();
|
|
195
|
+
} catch {}
|
|
196
|
+
}
|
|
197
|
+
async function throwIfAborted(signal, stream) {
|
|
198
|
+
if (!signal?.aborted) return;
|
|
199
|
+
if (stream !== void 0) await cancelStream(stream);
|
|
200
|
+
throwAbortError();
|
|
201
|
+
}
|
|
202
|
+
/** True when `pending` is a ToolCall whose _streamIndex equals `index`. */
|
|
203
|
+
function isPendingToolCallAtIndex(pending, index) {
|
|
204
|
+
return pending !== null && isToolCall(pending) && pending._streamIndex === index;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Append a fully-merged part to the message.
|
|
208
|
+
*
|
|
209
|
+
* - ContentPart -> message.content
|
|
210
|
+
* - ToolCall -> message.toolCalls (the `_streamIndex` routing key is
|
|
211
|
+
* registered in the map and stripped before storage).
|
|
212
|
+
* - ToolCallPart -> ignored (orphaned delta without a matching pending call)
|
|
213
|
+
*/
|
|
214
|
+
function flushPart(message, part, toolCallIndexMap) {
|
|
215
|
+
if (isContentPart(part)) {
|
|
216
|
+
message.content.push(part);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (isToolCall(part)) {
|
|
220
|
+
const streamIndex = part._streamIndex;
|
|
221
|
+
const stored = {
|
|
222
|
+
type: "function",
|
|
223
|
+
id: part.id,
|
|
224
|
+
name: part.name,
|
|
225
|
+
arguments: part.arguments,
|
|
226
|
+
extras: part.extras
|
|
227
|
+
};
|
|
228
|
+
const ordinal = message.toolCalls.length;
|
|
229
|
+
message.toolCalls.push(stored);
|
|
230
|
+
if (streamIndex !== void 0) toolCallIndexMap.set(streamIndex, ordinal);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Produce a shallow-ish copy of a StreamedMessagePart.
|
|
235
|
+
*
|
|
236
|
+
* This is intentionally minimal: we only need isolation for the mutable
|
|
237
|
+
* string fields that `mergeInPlace` mutates (text, think, arguments).
|
|
238
|
+
*/
|
|
239
|
+
function deepCopyPart(part) {
|
|
240
|
+
return structuredClone(part);
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/usage.ts
|
|
244
|
+
/**
|
|
245
|
+
* Compute total input tokens (other + cache read + cache creation).
|
|
246
|
+
*/
|
|
247
|
+
function inputTotal(usage) {
|
|
248
|
+
return usage.inputOther + usage.inputCacheRead + usage.inputCacheCreation;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Compute grand total tokens (input total + output).
|
|
252
|
+
*/
|
|
253
|
+
function grandTotal(usage) {
|
|
254
|
+
return inputTotal(usage) + usage.output;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Create a zero-valued TokenUsage.
|
|
258
|
+
*/
|
|
259
|
+
function emptyUsage() {
|
|
260
|
+
return {
|
|
261
|
+
inputOther: 0,
|
|
262
|
+
output: 0,
|
|
263
|
+
inputCacheRead: 0,
|
|
264
|
+
inputCacheCreation: 0
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Sum two TokenUsage values.
|
|
269
|
+
*/
|
|
270
|
+
function addUsage(a, b) {
|
|
271
|
+
return {
|
|
272
|
+
inputOther: a.inputOther + b.inputOther,
|
|
273
|
+
output: a.output + b.output,
|
|
274
|
+
inputCacheRead: a.inputCacheRead + b.inputCacheRead,
|
|
275
|
+
inputCacheCreation: a.inputCacheCreation + b.inputCacheCreation
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/index.ts
|
|
280
|
+
/**
|
|
281
|
+
* Concrete provider adapters stay off the root barrel because their SDK type
|
|
282
|
+
* graphs pollute downstream declaration bundles. Import them from subpaths:
|
|
283
|
+
* `@byfriends/kosong/providers/openai-compat`,
|
|
284
|
+
* `@byfriends/kosong/providers/openai-legacy`, etc.
|
|
285
|
+
*/
|
|
286
|
+
//#endregion
|
|
287
|
+
export { APIConnectionError, APIContextOverflowError, APIEmptyResponseError, APIStatusError, APITimeoutError, ChatProviderError, UNKNOWN_CAPABILITY, addUsage, catalogBaseUrl, catalogModelToCapability, catalogProviderModels, createAssistantMessage, createProvider, createToolMessage, createUserMessage, emptyUsage, extractText, generate, grandTotal, inferWireType, inputTotal, isContentPart, isToolCall, isToolCallPart, isUnknownCapability, mergeInPlace };
|