@graphorin/provider 0.5.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/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/llamacpp-server.d.ts +69 -0
- package/dist/adapters/llamacpp-server.d.ts.map +1 -0
- package/dist/adapters/llamacpp-server.js +51 -0
- package/dist/adapters/llamacpp-server.js.map +1 -0
- package/dist/adapters/ollama.d.ts +43 -0
- package/dist/adapters/ollama.d.ts.map +1 -0
- package/dist/adapters/ollama.js +246 -0
- package/dist/adapters/ollama.js.map +1 -0
- package/dist/adapters/openai-compatible.d.ts +48 -0
- package/dist/adapters/openai-compatible.d.ts.map +1 -0
- package/dist/adapters/openai-compatible.js +28 -0
- package/dist/adapters/openai-compatible.js.map +1 -0
- package/dist/adapters/vercel.d.ts +135 -0
- package/dist/adapters/vercel.d.ts.map +1 -0
- package/dist/adapters/vercel.js +306 -0
- package/dist/adapters/vercel.js.map +1 -0
- package/dist/counters/anthropic.d.ts +36 -0
- package/dist/counters/anthropic.d.ts.map +1 -0
- package/dist/counters/anthropic.js +63 -0
- package/dist/counters/anthropic.js.map +1 -0
- package/dist/counters/bedrock.d.ts +27 -0
- package/dist/counters/bedrock.d.ts.map +1 -0
- package/dist/counters/bedrock.js +29 -0
- package/dist/counters/bedrock.js.map +1 -0
- package/dist/counters/dispatcher.d.ts +34 -0
- package/dist/counters/dispatcher.d.ts.map +1 -0
- package/dist/counters/dispatcher.js +78 -0
- package/dist/counters/dispatcher.js.map +1 -0
- package/dist/counters/global.d.ts +26 -0
- package/dist/counters/global.d.ts.map +1 -0
- package/dist/counters/global.js +31 -0
- package/dist/counters/global.js.map +1 -0
- package/dist/counters/google.d.ts +27 -0
- package/dist/counters/google.d.ts.map +1 -0
- package/dist/counters/google.js +29 -0
- package/dist/counters/google.js.map +1 -0
- package/dist/counters/heuristic.d.ts +37 -0
- package/dist/counters/heuristic.d.ts.map +1 -0
- package/dist/counters/heuristic.js +64 -0
- package/dist/counters/heuristic.js.map +1 -0
- package/dist/counters/index.d.ts +9 -0
- package/dist/counters/index.js +10 -0
- package/dist/counters/js-tiktoken.d.ts +63 -0
- package/dist/counters/js-tiktoken.d.ts.map +1 -0
- package/dist/counters/js-tiktoken.js +73 -0
- package/dist/counters/js-tiktoken.js.map +1 -0
- package/dist/counters/serialize.d.ts +37 -0
- package/dist/counters/serialize.d.ts.map +1 -0
- package/dist/counters/serialize.js +57 -0
- package/dist/counters/serialize.js.map +1 -0
- package/dist/errors/errors.d.ts +167 -0
- package/dist/errors/errors.d.ts.map +1 -0
- package/dist/errors/errors.js +182 -0
- package/dist/errors/errors.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/abort.js +34 -0
- package/dist/internal/abort.js.map +1 -0
- package/dist/internal/http.js +163 -0
- package/dist/internal/http.js.map +1 -0
- package/dist/internal/openai-shaped.js +308 -0
- package/dist/internal/openai-shaped.js.map +1 -0
- package/dist/internal/sse.js +104 -0
- package/dist/internal/sse.js.map +1 -0
- package/dist/internal/url-utils.js +24 -0
- package/dist/internal/url-utils.js.map +1 -0
- package/dist/middleware/compose.d.ts +85 -0
- package/dist/middleware/compose.d.ts.map +1 -0
- package/dist/middleware/compose.js +174 -0
- package/dist/middleware/compose.js.map +1 -0
- package/dist/middleware/index.d.ts +10 -0
- package/dist/middleware/index.js +11 -0
- package/dist/middleware/production-hook.d.ts +27 -0
- package/dist/middleware/production-hook.d.ts.map +1 -0
- package/dist/middleware/production-hook.js +21 -0
- package/dist/middleware/production-hook.js.map +1 -0
- package/dist/middleware/with-cost-limit.d.ts +46 -0
- package/dist/middleware/with-cost-limit.d.ts.map +1 -0
- package/dist/middleware/with-cost-limit.js +66 -0
- package/dist/middleware/with-cost-limit.js.map +1 -0
- package/dist/middleware/with-cost-tracking.d.ts +91 -0
- package/dist/middleware/with-cost-tracking.d.ts.map +1 -0
- package/dist/middleware/with-cost-tracking.js +95 -0
- package/dist/middleware/with-cost-tracking.js.map +1 -0
- package/dist/middleware/with-fallback.d.ts +25 -0
- package/dist/middleware/with-fallback.d.ts.map +1 -0
- package/dist/middleware/with-fallback.js +84 -0
- package/dist/middleware/with-fallback.js.map +1 -0
- package/dist/middleware/with-rate-limit.d.ts +36 -0
- package/dist/middleware/with-rate-limit.d.ts.map +1 -0
- package/dist/middleware/with-rate-limit.js +141 -0
- package/dist/middleware/with-rate-limit.js.map +1 -0
- package/dist/middleware/with-redaction.d.ts +60 -0
- package/dist/middleware/with-redaction.d.ts.map +1 -0
- package/dist/middleware/with-redaction.js +305 -0
- package/dist/middleware/with-redaction.js.map +1 -0
- package/dist/middleware/with-retry.d.ts +26 -0
- package/dist/middleware/with-retry.d.ts.map +1 -0
- package/dist/middleware/with-retry.js +157 -0
- package/dist/middleware/with-retry.js.map +1 -0
- package/dist/middleware/with-tracing.d.ts +21 -0
- package/dist/middleware/with-tracing.d.ts.map +1 -0
- package/dist/middleware/with-tracing.js +55 -0
- package/dist/middleware/with-tracing.js.map +1 -0
- package/dist/model-tier/classify.d.ts +34 -0
- package/dist/model-tier/classify.d.ts.map +1 -0
- package/dist/model-tier/classify.js +117 -0
- package/dist/model-tier/classify.js.map +1 -0
- package/dist/model-tier/index.d.ts +2 -0
- package/dist/model-tier/index.js +3 -0
- package/dist/provider.d.ts +51 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +64 -0
- package/dist/provider.js.map +1 -0
- package/dist/reasoning/apply-policy.d.ts +22 -0
- package/dist/reasoning/apply-policy.d.ts.map +1 -0
- package/dist/reasoning/apply-policy.js +47 -0
- package/dist/reasoning/apply-policy.js.map +1 -0
- package/dist/reasoning/classify-contract.d.ts +41 -0
- package/dist/reasoning/classify-contract.d.ts.map +1 -0
- package/dist/reasoning/classify-contract.js +59 -0
- package/dist/reasoning/classify-contract.js.map +1 -0
- package/dist/reasoning/index.d.ts +4 -0
- package/dist/reasoning/index.js +5 -0
- package/dist/reasoning/retention.d.ts +39 -0
- package/dist/reasoning/retention.d.ts.map +1 -0
- package/dist/reasoning/retention.js +33 -0
- package/dist/reasoning/retention.js.map +1 -0
- package/dist/tool-examples.d.ts +14 -0
- package/dist/tool-examples.d.ts.map +1 -0
- package/dist/tool-examples.js +35 -0
- package/dist/tool-examples.js.map +1 -0
- package/dist/trust/classify-local-provider.d.ts +47 -0
- package/dist/trust/classify-local-provider.d.ts.map +1 -0
- package/dist/trust/classify-local-provider.js +164 -0
- package/dist/trust/classify-local-provider.js.map +1 -0
- package/dist/trust/index.d.ts +3 -0
- package/dist/trust/index.js +3 -0
- package/package.json +128 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/internal/abort.ts
|
|
2
|
+
/**
|
|
3
|
+
* Abort detection shared by the retry / fallback predicates (PS-2).
|
|
4
|
+
*
|
|
5
|
+
* A network failure surfaces as `ProviderHttpError{ status: 0, cause }`,
|
|
6
|
+
* and a user/agent abort surfaces the same way — the distinguishing
|
|
7
|
+
* signal is an `AbortError` (DOMException) somewhere on the `cause`
|
|
8
|
+
* chain. Network errors should be retried and failed over; an abort must
|
|
9
|
+
* not be, even though the retry/fallback loops also short-circuit on
|
|
10
|
+
* `req.signal?.aborted` independently.
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* True when `err` — or any error on its `cause` chain — is an abort
|
|
16
|
+
* (`name === 'AbortError'` or Node's `code === 'ABORT_ERR'`). Walks at
|
|
17
|
+
* most a few links so a cyclic / pathological cause chain cannot loop.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
function isAbortError(err) {
|
|
22
|
+
let cursor = err;
|
|
23
|
+
for (let depth = 0; depth < 8 && cursor !== null && typeof cursor === "object"; depth++) {
|
|
24
|
+
const e = cursor;
|
|
25
|
+
if (e.name === "AbortError" || e.code === "ABORT_ERR") return true;
|
|
26
|
+
if (e.cause === cursor) break;
|
|
27
|
+
cursor = e.cause;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { isAbortError };
|
|
34
|
+
//# sourceMappingURL=abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.js","names":["cursor: unknown"],"sources":["../../src/internal/abort.ts"],"sourcesContent":["/**\n * Abort detection shared by the retry / fallback predicates (PS-2).\n *\n * A network failure surfaces as `ProviderHttpError{ status: 0, cause }`,\n * and a user/agent abort surfaces the same way — the distinguishing\n * signal is an `AbortError` (DOMException) somewhere on the `cause`\n * chain. Network errors should be retried and failed over; an abort must\n * not be, even though the retry/fallback loops also short-circuit on\n * `req.signal?.aborted` independently.\n *\n * @packageDocumentation\n */\n\n/**\n * True when `err` — or any error on its `cause` chain — is an abort\n * (`name === 'AbortError'` or Node's `code === 'ABORT_ERR'`). Walks at\n * most a few links so a cyclic / pathological cause chain cannot loop.\n *\n * @internal\n */\nexport function isAbortError(err: unknown): boolean {\n let cursor: unknown = err;\n for (let depth = 0; depth < 8 && cursor !== null && typeof cursor === 'object'; depth++) {\n const e = cursor as { name?: unknown; code?: unknown; cause?: unknown };\n if (e.name === 'AbortError' || e.code === 'ABORT_ERR') return true;\n if (e.cause === cursor) break;\n cursor = e.cause;\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,aAAa,KAAuB;CAClD,IAAIA,SAAkB;AACtB,MAAK,IAAI,QAAQ,GAAG,QAAQ,KAAK,WAAW,QAAQ,OAAO,WAAW,UAAU,SAAS;EACvF,MAAM,IAAI;AACV,MAAI,EAAE,SAAS,gBAAgB,EAAE,SAAS,YAAa,QAAO;AAC9D,MAAI,EAAE,UAAU,OAAQ;AACxB,WAAS,EAAE;;AAEb,QAAO"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { ProviderHttpError } from "../errors/errors.js";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/http.ts
|
|
4
|
+
/**
|
|
5
|
+
* Convert a graphorin `Message` to the OpenAI-compatible chat-completion
|
|
6
|
+
* shape. The shape is the lingua-franca of the bundled local adapters
|
|
7
|
+
* (`llamaCppServerAdapter` and `openAICompatibleAdapter`); the
|
|
8
|
+
* native-Ollama path uses its own conversion.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
function toOpenAIChatMessages(messages) {
|
|
13
|
+
return messages.map((msg) => {
|
|
14
|
+
const out = {
|
|
15
|
+
role: msg.role,
|
|
16
|
+
content: typeof msg.content === "string" ? msg.content : flattenContent(msg.content)
|
|
17
|
+
};
|
|
18
|
+
if (msg.toolCalls !== void 0 && msg.toolCalls.length > 0) out.tool_calls = msg.toolCalls.map((tc) => ({
|
|
19
|
+
id: tc.toolCallId,
|
|
20
|
+
type: "function",
|
|
21
|
+
function: {
|
|
22
|
+
name: tc.toolName,
|
|
23
|
+
arguments: typeof tc.args === "string" ? tc.args : JSON.stringify(tc.args)
|
|
24
|
+
}
|
|
25
|
+
}));
|
|
26
|
+
if (msg.role === "tool" && msg.toolCallId !== void 0) out.tool_call_id = msg.toolCallId;
|
|
27
|
+
return out;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Convert a graphorin `Message` to Ollama's **native** `/api/chat` shape
|
|
32
|
+
* (PS-13). Unlike the OpenAI form, Ollama's Go server expects `tool_calls`
|
|
33
|
+
* with object `arguments` (a `map[string]any`, never a JSON string) and no
|
|
34
|
+
* `id` / `type` fields — sending those breaks its unmarshaller and any
|
|
35
|
+
* multi-turn replay of assistant tool calls.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
function toOllamaChatMessages(messages) {
|
|
40
|
+
return messages.map((msg) => {
|
|
41
|
+
const out = {
|
|
42
|
+
role: msg.role,
|
|
43
|
+
content: typeof msg.content === "string" ? msg.content : flattenContent(msg.content)
|
|
44
|
+
};
|
|
45
|
+
if (msg.toolCalls !== void 0 && msg.toolCalls.length > 0) out.tool_calls = msg.toolCalls.map((tc) => ({ function: {
|
|
46
|
+
name: tc.toolName,
|
|
47
|
+
arguments: toArgsObject(tc.args)
|
|
48
|
+
} }));
|
|
49
|
+
return out;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Coerce a tool-call `args` value into the object map Ollama expects. Strings
|
|
54
|
+
* (e.g. an OpenAI-style JSON blob produced upstream) are parsed leniently;
|
|
55
|
+
* anything that isn't a JSON object becomes `{}`.
|
|
56
|
+
*/
|
|
57
|
+
function toArgsObject(args) {
|
|
58
|
+
if (typeof args === "string") try {
|
|
59
|
+
const parsed = JSON.parse(args);
|
|
60
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
61
|
+
} catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
return typeof args === "object" && args !== null ? args : {};
|
|
65
|
+
}
|
|
66
|
+
function flattenContent(parts) {
|
|
67
|
+
const buffer = [];
|
|
68
|
+
for (const part of parts) {
|
|
69
|
+
if (typeof part === "string") {
|
|
70
|
+
buffer.push(part);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (typeof part === "object" && part !== null) {
|
|
74
|
+
const obj = part;
|
|
75
|
+
if (obj.type === "text" && typeof obj.text === "string") buffer.push(obj.text);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return buffer.join("");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Wrap a `fetch` call with HTTP error mapping. The helper does not
|
|
82
|
+
* assume any particular streaming format — callers receive the raw
|
|
83
|
+
* `Response` and dispatch on its body.
|
|
84
|
+
*
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* Default per-request timeout for the baseUrl adapters (PS-24). Scoped
|
|
89
|
+
* to time-to-response (headers): the timer is cleared the moment the
|
|
90
|
+
* server answers, so long streaming bodies are never killed — only a
|
|
91
|
+
* hung server that never responds. Generous because a cold local
|
|
92
|
+
* llama-server can take tens of seconds to load a model.
|
|
93
|
+
*
|
|
94
|
+
* @stable
|
|
95
|
+
*/
|
|
96
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 12e4;
|
|
97
|
+
async function callJsonHttp(args) {
|
|
98
|
+
const fetchImpl = args.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
99
|
+
const timeoutMs = args.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
100
|
+
const timeoutCtl = timeoutMs > 0 ? new AbortController() : void 0;
|
|
101
|
+
const timer = timeoutCtl !== void 0 ? setTimeout(() => timeoutCtl.abort(), timeoutMs) : void 0;
|
|
102
|
+
const signal = args.signal !== void 0 && timeoutCtl !== void 0 ? AbortSignal.any([args.signal, timeoutCtl.signal]) : args.signal ?? timeoutCtl?.signal;
|
|
103
|
+
let resp;
|
|
104
|
+
try {
|
|
105
|
+
resp = await fetchImpl(args.url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: args.headers,
|
|
108
|
+
body: JSON.stringify(args.body),
|
|
109
|
+
...signal !== void 0 ? { signal } : {}
|
|
110
|
+
});
|
|
111
|
+
} catch (cause) {
|
|
112
|
+
if (timeoutCtl?.signal.aborted === true && args.signal?.aborted !== true) throw new ProviderHttpError({
|
|
113
|
+
providerName: args.providerName,
|
|
114
|
+
status: 0,
|
|
115
|
+
message: `request timed out after ${timeoutMs}ms reaching ${args.url}`,
|
|
116
|
+
cause
|
|
117
|
+
});
|
|
118
|
+
throw new ProviderHttpError({
|
|
119
|
+
providerName: args.providerName,
|
|
120
|
+
status: 0,
|
|
121
|
+
message: `network error reaching ${args.url}`,
|
|
122
|
+
cause
|
|
123
|
+
});
|
|
124
|
+
} finally {
|
|
125
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
126
|
+
}
|
|
127
|
+
if (!resp.ok) {
|
|
128
|
+
const detail = await safeReadText(resp);
|
|
129
|
+
throw new ProviderHttpError({
|
|
130
|
+
providerName: args.providerName,
|
|
131
|
+
status: resp.status,
|
|
132
|
+
message: detail.length > 0 ? detail : resp.statusText
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return resp;
|
|
136
|
+
}
|
|
137
|
+
async function safeReadText(resp) {
|
|
138
|
+
try {
|
|
139
|
+
return await resp.text();
|
|
140
|
+
} catch {
|
|
141
|
+
return "";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Yield a `stream-start` `ProviderEvent` for an HTTP adapter.
|
|
146
|
+
*
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
function makeStreamStartEvent(args) {
|
|
150
|
+
return {
|
|
151
|
+
type: "stream-start",
|
|
152
|
+
metadata: {
|
|
153
|
+
providerName: args.providerName,
|
|
154
|
+
modelId: args.modelId,
|
|
155
|
+
...args.responseId !== void 0 ? { responseId: args.responseId } : {},
|
|
156
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
export { callJsonHttp, makeStreamStartEvent, toOllamaChatMessages, toOpenAIChatMessages };
|
|
163
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","names":["out: Record<string, unknown>","parsed: unknown","buffer: string[]","resp: Response"],"sources":["../../src/internal/http.ts"],"sourcesContent":["/**\n * Internal HTTP helpers shared across the local-LLM adapters.\n *\n * @internal\n */\n\nimport type { ProviderEvent } from '@graphorin/core';\n\nimport { ProviderHttpError } from '../errors/errors.js';\n\n/**\n * Convert a graphorin `Message` to the OpenAI-compatible chat-completion\n * shape. The shape is the lingua-franca of the bundled local adapters\n * (`llamaCppServerAdapter` and `openAICompatibleAdapter`); the\n * native-Ollama path uses its own conversion.\n *\n * @internal\n */\nexport function toOpenAIChatMessages(\n messages: ReadonlyArray<{\n readonly role: 'system' | 'user' | 'assistant' | 'tool';\n readonly content: string | ReadonlyArray<unknown>;\n readonly toolCalls?: ReadonlyArray<{\n readonly toolCallId: string;\n readonly toolName: string;\n readonly args: unknown;\n }>;\n readonly toolCallId?: string;\n }>,\n): ReadonlyArray<Record<string, unknown>> {\n return messages.map((msg) => {\n const out: Record<string, unknown> = {\n role: msg.role,\n content: typeof msg.content === 'string' ? msg.content : flattenContent(msg.content),\n };\n if (msg.toolCalls !== undefined && msg.toolCalls.length > 0) {\n out.tool_calls = msg.toolCalls.map((tc) => ({\n id: tc.toolCallId,\n type: 'function',\n function: {\n name: tc.toolName,\n arguments: typeof tc.args === 'string' ? tc.args : JSON.stringify(tc.args),\n },\n }));\n }\n if (msg.role === 'tool' && msg.toolCallId !== undefined) {\n out.tool_call_id = msg.toolCallId;\n }\n return out;\n });\n}\n\n/**\n * Convert a graphorin `Message` to Ollama's **native** `/api/chat` shape\n * (PS-13). Unlike the OpenAI form, Ollama's Go server expects `tool_calls`\n * with object `arguments` (a `map[string]any`, never a JSON string) and no\n * `id` / `type` fields — sending those breaks its unmarshaller and any\n * multi-turn replay of assistant tool calls.\n *\n * @internal\n */\nexport function toOllamaChatMessages(\n messages: ReadonlyArray<{\n readonly role: 'system' | 'user' | 'assistant' | 'tool';\n readonly content: string | ReadonlyArray<unknown>;\n readonly toolCalls?: ReadonlyArray<{\n readonly toolCallId: string;\n readonly toolName: string;\n readonly args: unknown;\n }>;\n readonly toolCallId?: string;\n }>,\n): ReadonlyArray<Record<string, unknown>> {\n return messages.map((msg) => {\n const out: Record<string, unknown> = {\n role: msg.role,\n content: typeof msg.content === 'string' ? msg.content : flattenContent(msg.content),\n };\n if (msg.toolCalls !== undefined && msg.toolCalls.length > 0) {\n out.tool_calls = msg.toolCalls.map((tc) => ({\n function: {\n name: tc.toolName,\n arguments: toArgsObject(tc.args),\n },\n }));\n }\n return out;\n });\n}\n\n/**\n * Coerce a tool-call `args` value into the object map Ollama expects. Strings\n * (e.g. an OpenAI-style JSON blob produced upstream) are parsed leniently;\n * anything that isn't a JSON object becomes `{}`.\n */\nfunction toArgsObject(args: unknown): Record<string, unknown> {\n if (typeof args === 'string') {\n try {\n const parsed: unknown = JSON.parse(args);\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n return typeof args === 'object' && args !== null ? (args as Record<string, unknown>) : {};\n}\n\nfunction flattenContent(parts: ReadonlyArray<unknown>): string {\n const buffer: string[] = [];\n for (const part of parts) {\n if (typeof part === 'string') {\n buffer.push(part);\n continue;\n }\n if (typeof part === 'object' && part !== null) {\n const obj = part as { type?: string; text?: string };\n if (obj.type === 'text' && typeof obj.text === 'string') {\n buffer.push(obj.text);\n }\n }\n }\n return buffer.join('');\n}\n\n/**\n * Wrap a `fetch` call with HTTP error mapping. The helper does not\n * assume any particular streaming format — callers receive the raw\n * `Response` and dispatch on its body.\n *\n * @internal\n */\n/**\n * Default per-request timeout for the baseUrl adapters (PS-24). Scoped\n * to time-to-response (headers): the timer is cleared the moment the\n * server answers, so long streaming bodies are never killed — only a\n * hung server that never responds. Generous because a cold local\n * llama-server can take tens of seconds to load a model.\n *\n * @stable\n */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;\n\nexport async function callJsonHttp(args: {\n readonly providerName: string;\n readonly url: string;\n readonly headers: Record<string, string>;\n readonly body: unknown;\n readonly signal?: AbortSignal;\n readonly fetchImpl?: typeof fetch;\n /**\n * Time-to-response budget (PS-24). Default\n * {@link DEFAULT_REQUEST_TIMEOUT_MS}; `0` disables.\n */\n readonly timeoutMs?: number;\n}): Promise<Response> {\n const fetchImpl = args.fetchImpl ?? globalThis.fetch.bind(globalThis);\n const timeoutMs = args.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;\n const timeoutCtl = timeoutMs > 0 ? new AbortController() : undefined;\n const timer =\n timeoutCtl !== undefined ? setTimeout(() => timeoutCtl.abort(), timeoutMs) : undefined;\n const signal =\n args.signal !== undefined && timeoutCtl !== undefined\n ? AbortSignal.any([args.signal, timeoutCtl.signal])\n : (args.signal ?? timeoutCtl?.signal);\n let resp: Response;\n try {\n resp = await fetchImpl(args.url, {\n method: 'POST',\n headers: args.headers,\n body: JSON.stringify(args.body),\n ...(signal !== undefined ? { signal } : {}),\n });\n } catch (cause) {\n if (timeoutCtl?.signal.aborted === true && args.signal?.aborted !== true) {\n throw new ProviderHttpError({\n providerName: args.providerName,\n status: 0,\n message: `request timed out after ${timeoutMs}ms reaching ${args.url}`,\n cause,\n });\n }\n throw new ProviderHttpError({\n providerName: args.providerName,\n status: 0,\n message: `network error reaching ${args.url}`,\n cause,\n });\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n if (!resp.ok) {\n const detail = await safeReadText(resp);\n throw new ProviderHttpError({\n providerName: args.providerName,\n status: resp.status,\n message: detail.length > 0 ? detail : resp.statusText,\n });\n }\n return resp;\n}\n\nasync function safeReadText(resp: Response): Promise<string> {\n try {\n return await resp.text();\n } catch {\n return '';\n }\n}\n\n/**\n * Yield a `stream-start` `ProviderEvent` for an HTTP adapter.\n *\n * @internal\n */\nexport function makeStreamStartEvent(args: {\n readonly providerName: string;\n readonly modelId: string;\n readonly responseId?: string;\n}): ProviderEvent {\n return {\n type: 'stream-start',\n metadata: {\n providerName: args.providerName,\n modelId: args.modelId,\n ...(args.responseId !== undefined ? { responseId: args.responseId } : {}),\n createdAt: new Date().toISOString(),\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAkBA,SAAgB,qBACd,UAUwC;AACxC,QAAO,SAAS,KAAK,QAAQ;EAC3B,MAAMA,MAA+B;GACnC,MAAM,IAAI;GACV,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,eAAe,IAAI,QAAQ;GACrF;AACD,MAAI,IAAI,cAAc,UAAa,IAAI,UAAU,SAAS,EACxD,KAAI,aAAa,IAAI,UAAU,KAAK,QAAQ;GAC1C,IAAI,GAAG;GACP,MAAM;GACN,UAAU;IACR,MAAM,GAAG;IACT,WAAW,OAAO,GAAG,SAAS,WAAW,GAAG,OAAO,KAAK,UAAU,GAAG,KAAK;IAC3E;GACF,EAAE;AAEL,MAAI,IAAI,SAAS,UAAU,IAAI,eAAe,OAC5C,KAAI,eAAe,IAAI;AAEzB,SAAO;GACP;;;;;;;;;;;AAYJ,SAAgB,qBACd,UAUwC;AACxC,QAAO,SAAS,KAAK,QAAQ;EAC3B,MAAMA,MAA+B;GACnC,MAAM,IAAI;GACV,SAAS,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU,eAAe,IAAI,QAAQ;GACrF;AACD,MAAI,IAAI,cAAc,UAAa,IAAI,UAAU,SAAS,EACxD,KAAI,aAAa,IAAI,UAAU,KAAK,QAAQ,EAC1C,UAAU;GACR,MAAM,GAAG;GACT,WAAW,aAAa,GAAG,KAAK;GACjC,EACF,EAAE;AAEL,SAAO;GACP;;;;;;;AAQJ,SAAS,aAAa,MAAwC;AAC5D,KAAI,OAAO,SAAS,SAClB,KAAI;EACF,MAAMC,SAAkB,KAAK,MAAM,KAAK;AACxC,SAAO,OAAO,WAAW,YAAY,WAAW,OAC3C,SACD,EAAE;SACA;AACN,SAAO,EAAE;;AAGb,QAAO,OAAO,SAAS,YAAY,SAAS,OAAQ,OAAmC,EAAE;;AAG3F,SAAS,eAAe,OAAuC;CAC7D,MAAMC,SAAmB,EAAE;AAC3B,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAO,KAAK,KAAK;AACjB;;AAEF,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;GAC7C,MAAM,MAAM;AACZ,OAAI,IAAI,SAAS,UAAU,OAAO,IAAI,SAAS,SAC7C,QAAO,KAAK,IAAI,KAAK;;;AAI3B,QAAO,OAAO,KAAK,GAAG;;;;;;;;;;;;;;;;;;AAmBxB,MAAa,6BAA6B;AAE1C,eAAsB,aAAa,MAYb;CACpB,MAAM,YAAY,KAAK,aAAa,WAAW,MAAM,KAAK,WAAW;CACrE,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,aAAa,YAAY,IAAI,IAAI,iBAAiB,GAAG;CAC3D,MAAM,QACJ,eAAe,SAAY,iBAAiB,WAAW,OAAO,EAAE,UAAU,GAAG;CAC/E,MAAM,SACJ,KAAK,WAAW,UAAa,eAAe,SACxC,YAAY,IAAI,CAAC,KAAK,QAAQ,WAAW,OAAO,CAAC,GAChD,KAAK,UAAU,YAAY;CAClC,IAAIC;AACJ,KAAI;AACF,SAAO,MAAM,UAAU,KAAK,KAAK;GAC/B,QAAQ;GACR,SAAS,KAAK;GACd,MAAM,KAAK,UAAU,KAAK,KAAK;GAC/B,GAAI,WAAW,SAAY,EAAE,QAAQ,GAAG,EAAE;GAC3C,CAAC;UACK,OAAO;AACd,MAAI,YAAY,OAAO,YAAY,QAAQ,KAAK,QAAQ,YAAY,KAClE,OAAM,IAAI,kBAAkB;GAC1B,cAAc,KAAK;GACnB,QAAQ;GACR,SAAS,2BAA2B,UAAU,cAAc,KAAK;GACjE;GACD,CAAC;AAEJ,QAAM,IAAI,kBAAkB;GAC1B,cAAc,KAAK;GACnB,QAAQ;GACR,SAAS,0BAA0B,KAAK;GACxC;GACD,CAAC;WACM;AACR,MAAI,UAAU,OAAW,cAAa,MAAM;;AAE9C,KAAI,CAAC,KAAK,IAAI;EACZ,MAAM,SAAS,MAAM,aAAa,KAAK;AACvC,QAAM,IAAI,kBAAkB;GAC1B,cAAc,KAAK;GACnB,QAAQ,KAAK;GACb,SAAS,OAAO,SAAS,IAAI,SAAS,KAAK;GAC5C,CAAC;;AAEJ,QAAO;;AAGT,eAAe,aAAa,MAAiC;AAC3D,KAAI;AACF,SAAO,MAAM,KAAK,MAAM;SAClB;AACN,SAAO;;;;;;;;AASX,SAAgB,qBAAqB,MAInB;AAChB,QAAO;EACL,MAAM;EACN,UAAU;GACR,cAAc,KAAK;GACnB,SAAS,KAAK;GACd,GAAI,KAAK,eAAe,SAAY,EAAE,YAAY,KAAK,YAAY,GAAG,EAAE;GACxE,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;EACF"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { LocalProviderInsecureTransportError, ProviderStreamParseError } from "../errors/errors.js";
|
|
2
|
+
import { applyReasoningPolicy } from "../reasoning/apply-policy.js";
|
|
3
|
+
import { resolveReasoningRetention } from "../reasoning/retention.js";
|
|
4
|
+
import { classifyLocalProvider } from "../trust/classify-local-provider.js";
|
|
5
|
+
import { callJsonHttp, makeStreamStartEvent, toOpenAIChatMessages } from "./http.js";
|
|
6
|
+
import { parseEventStream } from "./sse.js";
|
|
7
|
+
import { stripTrailingSlashes } from "./url-utils.js";
|
|
8
|
+
|
|
9
|
+
//#region src/internal/openai-shaped.ts
|
|
10
|
+
const DEFAULT_CAPABILITIES = {
|
|
11
|
+
streaming: true,
|
|
12
|
+
toolCalling: true,
|
|
13
|
+
parallelToolCalls: false,
|
|
14
|
+
multimodal: false,
|
|
15
|
+
structuredOutput: true,
|
|
16
|
+
reasoning: false,
|
|
17
|
+
contextWindow: 8192,
|
|
18
|
+
maxOutput: 4096,
|
|
19
|
+
reasoningContract: "optional"
|
|
20
|
+
};
|
|
21
|
+
const KNOWN_LOOPBACK_OVERRIDES_WARNED = /* @__PURE__ */ new Set();
|
|
22
|
+
/**
|
|
23
|
+
* Build a Graphorin `Provider` backed by an OpenAI-compatible HTTP
|
|
24
|
+
* server. Returns the wrapped object plus the resolved trust
|
|
25
|
+
* classification so the caller can attach it to span attributes if
|
|
26
|
+
* desired.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
function buildOpenAIShapedProvider(opts) {
|
|
31
|
+
const classification = classifyLocalProvider(opts.baseUrl);
|
|
32
|
+
if (classification.trust === "public-cleartext" && opts.allowInsecureTransport !== true) throw new LocalProviderInsecureTransportError(opts.baseUrl);
|
|
33
|
+
const log = opts.logger ?? defaultLogger;
|
|
34
|
+
if (classification.trust === "public-cleartext") log("warn", `[${opts.providerName}] allowInsecureTransport=true accepted for ${opts.baseUrl}`, { baseUrl: opts.baseUrl });
|
|
35
|
+
else if (classification.trust === "public-tls") log("warn", `[${opts.providerName}] public-TLS endpoint; treating as cloud-tier`, { baseUrl: opts.baseUrl });
|
|
36
|
+
else if (classification.trust === "private") log("warn", `[${opts.providerName}] private-network endpoint detected (${classification.reason})`, {
|
|
37
|
+
baseUrl: opts.baseUrl,
|
|
38
|
+
acceptsSensitivity: classification.acceptsSensitivity
|
|
39
|
+
});
|
|
40
|
+
const acceptsSensitivity = opts.acceptsSensitivity ?? classification.acceptsSensitivity;
|
|
41
|
+
if (opts.acceptsSensitivity !== void 0 && !sameSensitivity(opts.acceptsSensitivity, classification.acceptsSensitivity) && !KNOWN_LOOPBACK_OVERRIDES_WARNED.has(opts.baseUrl)) {
|
|
42
|
+
KNOWN_LOOPBACK_OVERRIDES_WARNED.add(opts.baseUrl);
|
|
43
|
+
log("info", `[${opts.providerName}] sensitivity override accepted; default would be [${classification.acceptsSensitivity.join(", ")}]`, { baseUrl: opts.baseUrl });
|
|
44
|
+
}
|
|
45
|
+
const capabilities = {
|
|
46
|
+
...DEFAULT_CAPABILITIES,
|
|
47
|
+
...opts.capabilities
|
|
48
|
+
};
|
|
49
|
+
const chatPath = opts.chatPath ?? "/v1/chat/completions";
|
|
50
|
+
const url = `${stripTrailingSlashes(opts.baseUrl)}${chatPath}`;
|
|
51
|
+
return {
|
|
52
|
+
provider: {
|
|
53
|
+
name: opts.providerName,
|
|
54
|
+
modelId: opts.model,
|
|
55
|
+
capabilities,
|
|
56
|
+
acceptsSensitivity,
|
|
57
|
+
stream(req) {
|
|
58
|
+
return streamOpenAIShaped(opts, url, applyOpenAIShapedPreflight(req, capabilities));
|
|
59
|
+
},
|
|
60
|
+
async generate(req) {
|
|
61
|
+
return generateOpenAIShaped(opts, url, applyOpenAIShapedPreflight(req, capabilities));
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
classification
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function applyOpenAIShapedPreflight(req, capabilities) {
|
|
68
|
+
const retention = resolveReasoningRetention({
|
|
69
|
+
...req.reasoningRetention !== void 0 ? { requested: req.reasoningRetention } : {},
|
|
70
|
+
...capabilities.reasoningContract !== void 0 ? { contract: capabilities.reasoningContract } : {}
|
|
71
|
+
});
|
|
72
|
+
if (retention === "pass-through-all") return req;
|
|
73
|
+
const filtered = applyReasoningPolicy({
|
|
74
|
+
messages: req.messages,
|
|
75
|
+
retention
|
|
76
|
+
});
|
|
77
|
+
if (filtered === req.messages) return req;
|
|
78
|
+
return {
|
|
79
|
+
...req,
|
|
80
|
+
messages: filtered
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function* streamOpenAIShaped(opts, url, req) {
|
|
84
|
+
const body = buildBody(opts.model, req, true, opts.capabilities?.structuredOutput ?? true);
|
|
85
|
+
const resp = await callJsonHttp({
|
|
86
|
+
providerName: opts.providerName,
|
|
87
|
+
url,
|
|
88
|
+
headers: buildHeaders(opts),
|
|
89
|
+
body,
|
|
90
|
+
...req.signal !== void 0 ? { signal: req.signal } : {},
|
|
91
|
+
...opts.fetchImpl !== void 0 ? { fetchImpl: opts.fetchImpl } : {},
|
|
92
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
93
|
+
});
|
|
94
|
+
yield makeStreamStartEvent({
|
|
95
|
+
providerName: opts.providerName,
|
|
96
|
+
modelId: opts.model
|
|
97
|
+
});
|
|
98
|
+
let finishReason = "stop";
|
|
99
|
+
let usage;
|
|
100
|
+
const toolCallBuffer = /* @__PURE__ */ new Map();
|
|
101
|
+
for await (const payload of parseEventStream(resp.body, req.signal !== void 0 ? { signal: req.signal } : {})) {
|
|
102
|
+
if (req.signal?.aborted) {
|
|
103
|
+
finishReason = "aborted";
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(payload);
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
throw new ProviderStreamParseError(opts.providerName, `failed to parse SSE chunk as JSON: ${cause.message}`, cause);
|
|
111
|
+
}
|
|
112
|
+
const choice = parsed.choices?.[0];
|
|
113
|
+
if (choice === void 0) {
|
|
114
|
+
if (parsed.usage !== void 0) usage = mapOpenAIUsage(parsed.usage);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const delta = choice.delta ?? {};
|
|
118
|
+
if (typeof delta.content === "string" && delta.content.length > 0) yield {
|
|
119
|
+
type: "text-delta",
|
|
120
|
+
delta: delta.content
|
|
121
|
+
};
|
|
122
|
+
if (typeof delta.reasoning_content === "string") {
|
|
123
|
+
const r = delta.reasoning_content;
|
|
124
|
+
if (r.length > 0) yield {
|
|
125
|
+
type: "reasoning-delta",
|
|
126
|
+
delta: r
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (Array.isArray(delta.tool_calls)) for (const tc of delta.tool_calls) {
|
|
130
|
+
const idx = tc.index ?? 0;
|
|
131
|
+
const slot = toolCallBuffer.get(idx) ?? {
|
|
132
|
+
id: "",
|
|
133
|
+
name: "",
|
|
134
|
+
args: "",
|
|
135
|
+
emittedStart: false
|
|
136
|
+
};
|
|
137
|
+
if (typeof tc.id === "string" && tc.id.length > 0) slot.id = tc.id;
|
|
138
|
+
if (tc.function?.name !== void 0) slot.name = tc.function.name;
|
|
139
|
+
if (typeof tc.function?.arguments === "string") slot.args += tc.function.arguments;
|
|
140
|
+
toolCallBuffer.set(idx, slot);
|
|
141
|
+
if (!slot.emittedStart && slot.id.length > 0 && slot.name.length > 0) {
|
|
142
|
+
yield {
|
|
143
|
+
type: "tool-call-start",
|
|
144
|
+
toolCallId: slot.id,
|
|
145
|
+
toolName: slot.name
|
|
146
|
+
};
|
|
147
|
+
slot.emittedStart = true;
|
|
148
|
+
}
|
|
149
|
+
if (typeof tc.function?.arguments === "string" && tc.function.arguments.length > 0) yield {
|
|
150
|
+
type: "tool-call-input-delta",
|
|
151
|
+
toolCallId: slot.id,
|
|
152
|
+
argsDelta: tc.function.arguments
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (typeof choice.finish_reason === "string") {
|
|
156
|
+
finishReason = mapFinishReason(choice.finish_reason);
|
|
157
|
+
for (const slot of toolCallBuffer.values()) {
|
|
158
|
+
const finalArgs = parseFinalArgs(slot.args);
|
|
159
|
+
yield {
|
|
160
|
+
type: "tool-call-end",
|
|
161
|
+
toolCallId: slot.id,
|
|
162
|
+
finalArgs
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (parsed.usage !== void 0) usage = mapOpenAIUsage(parsed.usage);
|
|
167
|
+
}
|
|
168
|
+
yield {
|
|
169
|
+
type: "finish",
|
|
170
|
+
finishReason,
|
|
171
|
+
usage: usage ?? {
|
|
172
|
+
promptTokens: 0,
|
|
173
|
+
completionTokens: 0,
|
|
174
|
+
totalTokens: 0
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function generateOpenAIShaped(opts, url, req) {
|
|
179
|
+
const body = buildBody(opts.model, req, false, opts.capabilities?.structuredOutput ?? true);
|
|
180
|
+
const resp = await callJsonHttp({
|
|
181
|
+
providerName: opts.providerName,
|
|
182
|
+
url,
|
|
183
|
+
headers: buildHeaders(opts),
|
|
184
|
+
body,
|
|
185
|
+
...req.signal !== void 0 ? { signal: req.signal } : {},
|
|
186
|
+
...opts.fetchImpl !== void 0 ? { fetchImpl: opts.fetchImpl } : {},
|
|
187
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {}
|
|
188
|
+
});
|
|
189
|
+
let json;
|
|
190
|
+
try {
|
|
191
|
+
json = await resp.json();
|
|
192
|
+
} catch (cause) {
|
|
193
|
+
throw new ProviderStreamParseError(opts.providerName, "response body was not valid JSON", cause);
|
|
194
|
+
}
|
|
195
|
+
const choice = json.choices?.[0];
|
|
196
|
+
const message = choice?.message ?? {};
|
|
197
|
+
const text = typeof message.content === "string" ? message.content : void 0;
|
|
198
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls.map((tc) => ({
|
|
199
|
+
toolCallId: tc.id ?? "",
|
|
200
|
+
toolName: tc.function?.name ?? "",
|
|
201
|
+
args: parseFinalArgs(typeof tc.function?.arguments === "string" ? tc.function.arguments : "")
|
|
202
|
+
})) : void 0;
|
|
203
|
+
return {
|
|
204
|
+
usage: mapOpenAIUsage(json.usage) ?? {
|
|
205
|
+
promptTokens: 0,
|
|
206
|
+
completionTokens: 0,
|
|
207
|
+
totalTokens: 0
|
|
208
|
+
},
|
|
209
|
+
finishReason: mapFinishReason(choice?.finish_reason),
|
|
210
|
+
...text !== void 0 ? { text } : {},
|
|
211
|
+
...toolCalls !== void 0 ? { toolCalls } : {}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function buildBody(model, req, stream, structuredOutput) {
|
|
215
|
+
const body = {
|
|
216
|
+
model,
|
|
217
|
+
messages: toOpenAIChatMessages(req.systemMessage !== void 0 ? [{
|
|
218
|
+
role: "system",
|
|
219
|
+
content: req.systemMessage
|
|
220
|
+
}, ...req.messages] : req.messages),
|
|
221
|
+
stream
|
|
222
|
+
};
|
|
223
|
+
if (req.temperature !== void 0) body.temperature = req.temperature;
|
|
224
|
+
if (req.maxTokens !== void 0) body.max_tokens = req.maxTokens;
|
|
225
|
+
if (req.tools !== void 0 && req.tools.length > 0) body.tools = req.tools.map((t) => ({
|
|
226
|
+
type: "function",
|
|
227
|
+
function: {
|
|
228
|
+
name: t.name,
|
|
229
|
+
...t.description !== void 0 ? { description: t.description } : {},
|
|
230
|
+
parameters: t.inputSchema
|
|
231
|
+
}
|
|
232
|
+
}));
|
|
233
|
+
if (req.toolChoice !== void 0) body.tool_choice = mapToolChoice(req.toolChoice);
|
|
234
|
+
if (structuredOutput && req.outputType?.kind === "structured") body.response_format = req.outputType.jsonSchema !== void 0 ? {
|
|
235
|
+
type: "json_schema",
|
|
236
|
+
json_schema: {
|
|
237
|
+
name: "output",
|
|
238
|
+
schema: req.outputType.jsonSchema,
|
|
239
|
+
strict: true
|
|
240
|
+
}
|
|
241
|
+
} : { type: "json_object" };
|
|
242
|
+
if (req.providerOptions !== void 0) Object.assign(body, req.providerOptions);
|
|
243
|
+
return body;
|
|
244
|
+
}
|
|
245
|
+
function buildHeaders(opts) {
|
|
246
|
+
const headers = {
|
|
247
|
+
"content-type": "application/json",
|
|
248
|
+
accept: "application/json",
|
|
249
|
+
...opts.headers
|
|
250
|
+
};
|
|
251
|
+
if (opts.apiKey !== void 0 && opts.apiKey.length > 0) headers.authorization = `Bearer ${opts.apiKey}`;
|
|
252
|
+
return headers;
|
|
253
|
+
}
|
|
254
|
+
function mapToolChoice(value) {
|
|
255
|
+
if (value === "auto" || value === "none" || value === "required") return value;
|
|
256
|
+
if (typeof value === "object" && value !== null && "tool" in value) return {
|
|
257
|
+
type: "function",
|
|
258
|
+
function: { name: value.tool }
|
|
259
|
+
};
|
|
260
|
+
return "auto";
|
|
261
|
+
}
|
|
262
|
+
function mapFinishReason(value) {
|
|
263
|
+
switch (value) {
|
|
264
|
+
case "stop":
|
|
265
|
+
case "length":
|
|
266
|
+
case "content-filter": return value;
|
|
267
|
+
case "content_filter": return "content-filter";
|
|
268
|
+
case "tool_calls":
|
|
269
|
+
case "function_call": return "tool-calls";
|
|
270
|
+
case null:
|
|
271
|
+
case void 0: return "stop";
|
|
272
|
+
default: return "stop";
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function mapOpenAIUsage(input) {
|
|
276
|
+
if (input === void 0 || input === null || typeof input !== "object") return void 0;
|
|
277
|
+
const u = input;
|
|
278
|
+
const promptTokens = u.prompt_tokens ?? 0;
|
|
279
|
+
const completionTokens = u.completion_tokens ?? 0;
|
|
280
|
+
return {
|
|
281
|
+
promptTokens,
|
|
282
|
+
completionTokens,
|
|
283
|
+
totalTokens: u.total_tokens ?? promptTokens + completionTokens,
|
|
284
|
+
...u.reasoning_tokens !== void 0 ? { reasoningTokens: u.reasoning_tokens } : {}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function parseFinalArgs(raw) {
|
|
288
|
+
if (raw.length === 0) return {};
|
|
289
|
+
try {
|
|
290
|
+
return JSON.parse(raw);
|
|
291
|
+
} catch {
|
|
292
|
+
return raw;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function sameSensitivity(a, b) {
|
|
296
|
+
if (a.length !== b.length) return false;
|
|
297
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
function defaultLogger(level, message, meta) {
|
|
301
|
+
const fn = level === "warn" ? console.warn : console.info;
|
|
302
|
+
if (meta !== void 0) fn(`[graphorin/provider] ${message}`, meta);
|
|
303
|
+
else fn(`[graphorin/provider] ${message}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
//#endregion
|
|
307
|
+
export { buildOpenAIShapedProvider };
|
|
308
|
+
//# sourceMappingURL=openai-shaped.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-shaped.js","names":["DEFAULT_CAPABILITIES: ProviderCapabilities","capabilities: ProviderCapabilities","finishReason: FinishReason","usage: Usage | undefined","parsed: OpenAIChunk","json: OpenAIChunk","body: Record<string, unknown>","headers: Record<string, string>"],"sources":["../../src/internal/openai-shaped.ts"],"sourcesContent":["/**\n * Shared implementation backing the two OpenAI-compatible adapters\n * (`llamaCppServerAdapter` and `openAICompatibleAdapter`). The wire\n * shape is identical for the `/v1/chat/completions` endpoint, so we\n * factor the streaming + one-shot logic here and the adapters become\n * thin shells declaring trust class, defaults, and capability flags.\n *\n * @internal\n */\n\nimport type {\n FinishReason,\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Sensitivity,\n Usage,\n} from '@graphorin/core';\n\nimport { LocalProviderInsecureTransportError, ProviderStreamParseError } from '../errors/errors.js';\nimport { applyReasoningPolicy } from '../reasoning/apply-policy.js';\nimport { resolveReasoningRetention } from '../reasoning/retention.js';\nimport {\n classifyLocalProvider,\n type LocalProviderClassification,\n} from '../trust/classify-local-provider.js';\nimport { callJsonHttp, makeStreamStartEvent, toOpenAIChatMessages } from './http.js';\nimport { parseEventStream } from './sse.js';\nimport { stripTrailingSlashes } from './url-utils.js';\n\n/**\n * Options shared by every OpenAI-compatible adapter.\n *\n * @internal\n */\nexport interface OpenAIShapedOptions {\n readonly providerName: string;\n readonly model: string;\n readonly baseUrl: string;\n readonly chatPath?: string;\n readonly apiKey?: string;\n readonly headers?: Readonly<Record<string, string>>;\n readonly fetchImpl?: typeof fetch;\n readonly allowInsecureTransport?: boolean;\n readonly capabilities?: Partial<ProviderCapabilities>;\n /**\n * Time-to-response budget per request (PS-24). Default\n * `DEFAULT_REQUEST_TIMEOUT_MS` (120s); `0` disables.\n */\n readonly timeoutMs?: number;\n /**\n * Overrides the default `acceptsSensitivity` value derived from the\n * trust classifier. Loud users can opt out of the conservative\n * default, but the framework still emits one WARN per process so\n * the override is visible.\n */\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n /**\n * Optional log sink. When unset, the adapter falls back to\n * `console.warn`. Tests pass a fixture-driven sink to silence the\n * console.\n */\n readonly logger?: (level: 'warn' | 'info', message: string, meta?: object) => void;\n}\n\nconst DEFAULT_CAPABILITIES: ProviderCapabilities = {\n streaming: true,\n toolCalling: true,\n parallelToolCalls: false,\n multimodal: false,\n structuredOutput: true,\n reasoning: false,\n contextWindow: 8_192,\n maxOutput: 4_096,\n reasoningContract: 'optional',\n};\n\nconst KNOWN_LOOPBACK_OVERRIDES_WARNED = new Set<string>();\n\n/**\n * Build a Graphorin `Provider` backed by an OpenAI-compatible HTTP\n * server. Returns the wrapped object plus the resolved trust\n * classification so the caller can attach it to span attributes if\n * desired.\n *\n * @internal\n */\nexport function buildOpenAIShapedProvider(opts: OpenAIShapedOptions): {\n readonly provider: Provider;\n readonly classification: LocalProviderClassification;\n} {\n const classification = classifyLocalProvider(opts.baseUrl);\n if (classification.trust === 'public-cleartext' && opts.allowInsecureTransport !== true) {\n throw new LocalProviderInsecureTransportError(opts.baseUrl);\n }\n const log = opts.logger ?? defaultLogger;\n if (classification.trust === 'public-cleartext') {\n log('warn', `[${opts.providerName}] allowInsecureTransport=true accepted for ${opts.baseUrl}`, {\n baseUrl: opts.baseUrl,\n });\n } else if (classification.trust === 'public-tls') {\n log('warn', `[${opts.providerName}] public-TLS endpoint; treating as cloud-tier`, {\n baseUrl: opts.baseUrl,\n });\n } else if (classification.trust === 'private') {\n log(\n 'warn',\n `[${opts.providerName}] private-network endpoint detected (${classification.reason})`,\n {\n baseUrl: opts.baseUrl,\n acceptsSensitivity: classification.acceptsSensitivity,\n },\n );\n }\n\n const acceptsSensitivity = opts.acceptsSensitivity ?? classification.acceptsSensitivity;\n if (\n opts.acceptsSensitivity !== undefined &&\n !sameSensitivity(opts.acceptsSensitivity, classification.acceptsSensitivity) &&\n !KNOWN_LOOPBACK_OVERRIDES_WARNED.has(opts.baseUrl)\n ) {\n KNOWN_LOOPBACK_OVERRIDES_WARNED.add(opts.baseUrl);\n log(\n 'info',\n `[${opts.providerName}] sensitivity override accepted; default would be [${classification.acceptsSensitivity.join(', ')}]`,\n {\n baseUrl: opts.baseUrl,\n },\n );\n }\n\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n ...opts.capabilities,\n };\n const chatPath = opts.chatPath ?? '/v1/chat/completions';\n const url = `${stripTrailingSlashes(opts.baseUrl)}${chatPath}`;\n\n const provider: Provider = {\n name: opts.providerName,\n modelId: opts.model,\n capabilities,\n acceptsSensitivity,\n stream(req) {\n return streamOpenAIShaped(opts, url, applyOpenAIShapedPreflight(req, capabilities));\n },\n async generate(req) {\n return generateOpenAIShaped(opts, url, applyOpenAIShapedPreflight(req, capabilities));\n },\n };\n\n return { provider, classification };\n}\n\nfunction applyOpenAIShapedPreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nasync function* streamOpenAIShaped(\n opts: OpenAIShapedOptions,\n url: string,\n req: ProviderRequest,\n): AsyncIterable<ProviderEvent> {\n const body = buildBody(opts.model, req, true, opts.capabilities?.structuredOutput ?? true);\n const resp = await callJsonHttp({\n providerName: opts.providerName,\n url,\n headers: buildHeaders(opts),\n body,\n ...(req.signal !== undefined ? { signal: req.signal } : {}),\n ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),\n ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n });\n\n yield makeStreamStartEvent({ providerName: opts.providerName, modelId: opts.model });\n\n let finishReason: FinishReason = 'stop';\n let usage: Usage | undefined;\n const toolCallBuffer = new Map<\n number,\n { id: string; name: string; args: string; emittedStart: boolean }\n >();\n\n for await (const payload of parseEventStream(\n resp.body,\n req.signal !== undefined ? { signal: req.signal } : {},\n )) {\n if (req.signal?.aborted) {\n // PS-12: report the abort honestly instead of the default 'stop'.\n finishReason = 'aborted';\n break;\n }\n let parsed: OpenAIChunk;\n try {\n parsed = JSON.parse(payload) as OpenAIChunk;\n } catch (cause) {\n throw new ProviderStreamParseError(\n opts.providerName,\n `failed to parse SSE chunk as JSON: ${(cause as Error).message}`,\n cause,\n );\n }\n const choice = parsed.choices?.[0];\n if (choice === undefined) {\n if (parsed.usage !== undefined) usage = mapOpenAIUsage(parsed.usage);\n continue;\n }\n const delta = choice.delta ?? {};\n if (typeof delta.content === 'string' && delta.content.length > 0) {\n yield { type: 'text-delta', delta: delta.content };\n }\n if (typeof (delta as { reasoning_content?: unknown }).reasoning_content === 'string') {\n const r = (delta as { reasoning_content: string }).reasoning_content;\n if (r.length > 0) yield { type: 'reasoning-delta', delta: r };\n }\n if (Array.isArray(delta.tool_calls)) {\n for (const tc of delta.tool_calls) {\n const idx = tc.index ?? 0;\n const slot = toolCallBuffer.get(idx) ?? {\n id: '',\n name: '',\n args: '',\n emittedStart: false,\n };\n if (typeof tc.id === 'string' && tc.id.length > 0) slot.id = tc.id;\n if (tc.function?.name !== undefined) slot.name = tc.function.name;\n if (typeof tc.function?.arguments === 'string') slot.args += tc.function.arguments;\n toolCallBuffer.set(idx, slot);\n if (!slot.emittedStart && slot.id.length > 0 && slot.name.length > 0) {\n yield {\n type: 'tool-call-start',\n toolCallId: slot.id,\n toolName: slot.name,\n };\n slot.emittedStart = true;\n }\n if (typeof tc.function?.arguments === 'string' && tc.function.arguments.length > 0) {\n yield {\n type: 'tool-call-input-delta',\n toolCallId: slot.id,\n argsDelta: tc.function.arguments,\n };\n }\n }\n }\n if (typeof choice.finish_reason === 'string') {\n finishReason = mapFinishReason(choice.finish_reason);\n for (const slot of toolCallBuffer.values()) {\n const finalArgs = parseFinalArgs(slot.args);\n yield {\n type: 'tool-call-end',\n toolCallId: slot.id,\n finalArgs,\n };\n }\n }\n if (parsed.usage !== undefined) usage = mapOpenAIUsage(parsed.usage);\n }\n\n yield {\n type: 'finish',\n finishReason,\n usage: usage ?? { promptTokens: 0, completionTokens: 0, totalTokens: 0 },\n };\n}\n\nasync function generateOpenAIShaped(\n opts: OpenAIShapedOptions,\n url: string,\n req: ProviderRequest,\n): Promise<ProviderResponse> {\n const body = buildBody(opts.model, req, false, opts.capabilities?.structuredOutput ?? true);\n const resp = await callJsonHttp({\n providerName: opts.providerName,\n url,\n headers: buildHeaders(opts),\n body,\n ...(req.signal !== undefined ? { signal: req.signal } : {}),\n ...(opts.fetchImpl !== undefined ? { fetchImpl: opts.fetchImpl } : {}),\n ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n });\n let json: OpenAIChunk;\n try {\n json = (await resp.json()) as OpenAIChunk;\n } catch (cause) {\n throw new ProviderStreamParseError(\n opts.providerName,\n 'response body was not valid JSON',\n cause,\n );\n }\n const choice = json.choices?.[0];\n const message = choice?.message ?? {};\n const text = typeof message.content === 'string' ? message.content : undefined;\n const toolCalls = Array.isArray(message.tool_calls)\n ? message.tool_calls.map((tc) => ({\n toolCallId: tc.id ?? '',\n toolName: tc.function?.name ?? '',\n args: parseFinalArgs(\n typeof tc.function?.arguments === 'string' ? tc.function.arguments : '',\n ),\n }))\n : undefined;\n const usage = mapOpenAIUsage(json.usage) ?? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n };\n return {\n usage,\n finishReason: mapFinishReason(choice?.finish_reason),\n ...(text !== undefined ? { text } : {}),\n ...(toolCalls !== undefined ? { toolCalls } : {}),\n };\n}\n\nfunction buildBody(\n model: string,\n req: ProviderRequest,\n stream: boolean,\n structuredOutput: boolean,\n): Record<string, unknown> {\n const messages =\n req.systemMessage !== undefined\n ? [{ role: 'system' as const, content: req.systemMessage }, ...req.messages]\n : req.messages;\n const body: Record<string, unknown> = {\n model,\n messages: toOpenAIChatMessages(messages),\n stream,\n };\n if (req.temperature !== undefined) body.temperature = req.temperature;\n if (req.maxTokens !== undefined) body.max_tokens = req.maxTokens;\n if (req.tools !== undefined && req.tools.length > 0) {\n body.tools = req.tools.map((t) => ({\n type: 'function',\n function: {\n name: t.name,\n ...(t.description !== undefined ? { description: t.description } : {}),\n parameters: t.inputSchema,\n },\n }));\n }\n if (req.toolChoice !== undefined) {\n body.tool_choice = mapToolChoice(req.toolChoice);\n }\n // PS-24: structured output finally reaches the wire — gated on the\n // declared capability so a structuredOutput:false override keeps the\n // request clean for servers that reject response_format.\n if (structuredOutput && req.outputType?.kind === 'structured') {\n body.response_format =\n req.outputType.jsonSchema !== undefined\n ? {\n type: 'json_schema',\n json_schema: { name: 'output', schema: req.outputType.jsonSchema, strict: true },\n }\n : { type: 'json_object' };\n }\n if (req.providerOptions !== undefined) {\n Object.assign(body, req.providerOptions);\n }\n return body;\n}\n\nfunction buildHeaders(opts: OpenAIShapedOptions): Record<string, string> {\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: 'application/json',\n ...opts.headers,\n };\n if (opts.apiKey !== undefined && opts.apiKey.length > 0) {\n headers.authorization = `Bearer ${opts.apiKey}`;\n }\n return headers;\n}\n\nfunction mapToolChoice(value: unknown): unknown {\n if (value === 'auto' || value === 'none' || value === 'required') return value;\n if (typeof value === 'object' && value !== null && 'tool' in value) {\n return { type: 'function', function: { name: (value as { tool: string }).tool } };\n }\n return 'auto';\n}\n\nfunction mapFinishReason(value: string | null | undefined): FinishReason {\n switch (value) {\n case 'stop':\n case 'length':\n case 'content-filter':\n return value;\n // PS-12: the OpenAI wire format spells it with an underscore; the dashed\n // form alone never matched, so content-filtered completions reported 'stop'.\n case 'content_filter':\n return 'content-filter';\n case 'tool_calls':\n case 'function_call':\n return 'tool-calls';\n case null:\n case undefined:\n return 'stop';\n default:\n return 'stop';\n }\n}\n\nfunction mapOpenAIUsage(input: unknown): Usage | undefined {\n if (input === undefined || input === null || typeof input !== 'object') return undefined;\n const u = input as {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n reasoning_tokens?: number;\n };\n const promptTokens = u.prompt_tokens ?? 0;\n const completionTokens = u.completion_tokens ?? 0;\n const totalTokens = u.total_tokens ?? promptTokens + completionTokens;\n const usage: Usage = {\n promptTokens,\n completionTokens,\n totalTokens,\n ...(u.reasoning_tokens !== undefined ? { reasoningTokens: u.reasoning_tokens } : {}),\n };\n return usage;\n}\n\nfunction parseFinalArgs(raw: string): unknown {\n if (raw.length === 0) return {};\n try {\n return JSON.parse(raw);\n } catch {\n return raw;\n }\n}\n\nfunction sameSensitivity(a: ReadonlyArray<Sensitivity>, b: ReadonlyArray<Sensitivity>): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction defaultLogger(level: 'warn' | 'info', message: string, meta?: object): void {\n const fn = level === 'warn' ? console.warn : console.info;\n if (meta !== undefined) {\n fn(`[graphorin/provider] ${message}`, meta);\n } else {\n fn(`[graphorin/provider] ${message}`);\n }\n}\n\ninterface OpenAIChunk {\n readonly id?: string;\n readonly choices?: ReadonlyArray<{\n readonly delta?: {\n readonly content?: string;\n readonly tool_calls?: ReadonlyArray<{\n readonly index?: number;\n readonly id?: string;\n readonly type?: string;\n readonly function?: { readonly name?: string; readonly arguments?: string };\n }>;\n readonly reasoning_content?: string;\n };\n readonly message?: {\n readonly content?: string | null;\n readonly tool_calls?: ReadonlyArray<{\n readonly id?: string;\n readonly type?: string;\n readonly function?: { readonly name?: string; readonly arguments?: string };\n }>;\n };\n readonly finish_reason?: string | null;\n }>;\n readonly usage?: {\n readonly prompt_tokens?: number;\n readonly completion_tokens?: number;\n readonly total_tokens?: number;\n readonly reasoning_tokens?: number;\n };\n}\n"],"mappings":"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"}
|