agentfootprint 9.72.0 → 9.74.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 +160 -0
- package/dist/adapters/identity/azure.js +306 -0
- package/dist/adapters/identity/azure.js.map +1 -0
- package/dist/adapters/llm/FoundryLocalProvider.js +992 -0
- package/dist/adapters/llm/FoundryLocalProvider.js.map +1 -0
- package/dist/adapters/llm/FoundryProvider.js +273 -0
- package/dist/adapters/llm/FoundryProvider.js.map +1 -0
- package/dist/adapters/llm/OllamaProvider.js +171 -12
- package/dist/adapters/llm/OllamaProvider.js.map +1 -1
- package/dist/adapters/llm/OpenAIProvider.js +117 -5
- package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/adapters/llm/createProvider.js +143 -10
- package/dist/adapters/llm/createProvider.js.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/esm/adapters/identity/azure.d.ts +188 -0
- package/dist/esm/adapters/identity/azure.js +302 -0
- package/dist/esm/adapters/identity/azure.js.map +1 -0
- package/dist/esm/adapters/llm/FoundryLocalProvider.d.ts +215 -0
- package/dist/esm/adapters/llm/FoundryLocalProvider.js +986 -0
- package/dist/esm/adapters/llm/FoundryLocalProvider.js.map +1 -0
- package/dist/esm/adapters/llm/FoundryProvider.d.ts +178 -0
- package/dist/esm/adapters/llm/FoundryProvider.js +268 -0
- package/dist/esm/adapters/llm/FoundryProvider.js.map +1 -0
- package/dist/esm/adapters/llm/OllamaProvider.js +171 -12
- package/dist/esm/adapters/llm/OllamaProvider.js.map +1 -1
- package/dist/esm/adapters/llm/OpenAIProvider.d.ts +111 -0
- package/dist/esm/adapters/llm/OpenAIProvider.js +115 -4
- package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
- package/dist/esm/adapters/llm/createProvider.d.ts +48 -12
- package/dist/esm/adapters/llm/createProvider.js +143 -10
- package/dist/esm/adapters/llm/createProvider.js.map +1 -1
- package/dist/esm/adapters/types.d.ts +4 -2
- package/dist/esm/adapters/types.js.map +1 -1
- package/dist/esm/identity.d.ts +1 -0
- package/dist/esm/identity.js +9 -0
- package/dist/esm/identity.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/providers.d.ts +5 -0
- package/dist/esm/providers.js +14 -0
- package/dist/esm/providers.js.map +1 -1
- package/dist/identity.js +14 -1
- package/dist/identity.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers.js +20 -1
- package/dist/providers.js.map +1 -1
- package/dist/types/adapters/identity/azure.d.ts +189 -0
- package/dist/types/adapters/identity/azure.d.ts.map +1 -0
- package/dist/types/adapters/llm/FoundryLocalProvider.d.ts +216 -0
- package/dist/types/adapters/llm/FoundryLocalProvider.d.ts.map +1 -0
- package/dist/types/adapters/llm/FoundryProvider.d.ts +179 -0
- package/dist/types/adapters/llm/FoundryProvider.d.ts.map +1 -0
- package/dist/types/adapters/llm/OllamaProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/OpenAIProvider.d.ts +111 -0
- package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
- package/dist/types/adapters/llm/createProvider.d.ts +48 -12
- package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
- package/dist/types/adapters/types.d.ts +4 -2
- package/dist/types/adapters/types.d.ts.map +1 -1
- package/dist/types/identity.d.ts +1 -0
- package/dist/types/identity.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/providers.d.ts +5 -0
- package/dist/types/providers.d.ts.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FoundryLocalProvider — on-device models over Foundry Local's
|
|
3
|
+
* OpenAI-compatible `/v1/chat/completions`.
|
|
4
|
+
*
|
|
5
|
+
* Pattern: Adapter (GoF) + Ports-and-Adapters (Cockburn 2005).
|
|
6
|
+
* Role: Outer ring — translates `LLMRequest`/`LLMResponse` to/from the
|
|
7
|
+
* wire Foundry Local serves on localhost. Knows nothing about
|
|
8
|
+
* agents, recorders, or compositions.
|
|
9
|
+
* Emits: N/A.
|
|
10
|
+
*
|
|
11
|
+
* ─── Why this exists ─────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* The adapter ladder is `mock()` → a local model → a paid API, and
|
|
14
|
+
* `ollama()` is the proof that the middle rung is worth owning: zero
|
|
15
|
+
* dependencies, honest refusals, real token counts. Foundry Local is
|
|
16
|
+
* Microsoft's runtime for the same rung — ONNX under the hood, models
|
|
17
|
+
* pulled with `foundry model run <alias>`, no key, no account — and a
|
|
18
|
+
* Windows or macOS machine that has it installed deserves the same
|
|
19
|
+
* one-import experience. So this file owns that wire the way
|
|
20
|
+
* `OllamaProvider.ts` owns Ollama's:
|
|
21
|
+
*
|
|
22
|
+
* • ZERO dependencies — one `fetch` POST and SSE. The official
|
|
23
|
+
* `foundry-local-sdk` is NOT imported; its manager is accepted
|
|
24
|
+
* duck-typed (see {@link FoundryLocalProviderOptions.manager}) so a
|
|
25
|
+
* consumer who already uses it can hand over the discovered URL
|
|
26
|
+
* without this package gaining a dependency.
|
|
27
|
+
* • HONEST REFUSALS — a typed {@link FoundryLocalUnavailableError}
|
|
28
|
+
* that names the endpoint it tried and the command to run. The
|
|
29
|
+
* service's port is DYNAMIC per start, which makes "nothing is
|
|
30
|
+
* answering" the most likely first failure — so that message
|
|
31
|
+
* carries the discovery command, not just the start command. A
|
|
32
|
+
* failure the service reports IN BAND — an `error` frame on an
|
|
33
|
+
* already-200 stream, the out-of-memory a laptop runtime really does
|
|
34
|
+
* hit — is RAISED the same way, never handed over as a shorter
|
|
35
|
+
* answer that reads like a clean stop.
|
|
36
|
+
* • REAL TOKEN COUNTS while streaming — `stream_options:
|
|
37
|
+
* { include_usage: true }` is always sent. This is OUR wire, a
|
|
38
|
+
* documented Foundry Local surface, not an arbitrary
|
|
39
|
+
* OpenAI-compatible server — so the caution that made
|
|
40
|
+
* `openai({ baseURL })` withhold the field (and silently zero every
|
|
41
|
+
* local token count until 9.73.0) does not apply here.
|
|
42
|
+
*
|
|
43
|
+
* ─── Wire realities this file owns ───────────────────────────────────
|
|
44
|
+
*
|
|
45
|
+
* • THE PORT IS DYNAMIC. Every `foundry server start` may pick a new
|
|
46
|
+
* port; the docs' own REST example shows `http://localhost:5272` and
|
|
47
|
+
* that is the default here, but the truthful discovery is
|
|
48
|
+
* `foundry server status` (or the SDK manager's `.urls`). Note the
|
|
49
|
+
* CLI group was RENAMED from `foundry service` to `foundry server` —
|
|
50
|
+
* every message in this file uses the NEW spelling.
|
|
51
|
+
* • ALIASES vs VARIANT IDS. The catalog speaks in aliases
|
|
52
|
+
* (`qwen2.5-0.5b`) that fan out to hardware variants
|
|
53
|
+
* (`qwen2.5-0.5b-instruct-generic-cpu:1`), but REST chat calls take
|
|
54
|
+
* the FULL variant id. This adapter resolves an alias through
|
|
55
|
+
* `GET /foundry/list` — first matching variant wins, because the
|
|
56
|
+
* list's order IS the service's priority order — and caches the
|
|
57
|
+
* answer per provider instance, HIT OR MISS: exactly one catalog
|
|
58
|
+
* attempt per name, so an alias the catalog never answers for cannot
|
|
59
|
+
* re-ask before every call. A fresh provider is the retry. A name that
|
|
60
|
+
* already carries a variant's execution-provider suffix
|
|
61
|
+
* (`-cpu`/`-gpu`/`-npu`, optional `:version`) is used as-is with no
|
|
62
|
+
* catalog round-trip.
|
|
63
|
+
* • NO API KEY EXISTS. The docs' own samples pass placeholders. This
|
|
64
|
+
* adapter sends no `Authorization` header at all — there is nothing
|
|
65
|
+
* to put in one, and an invented value would only end up in somebody's
|
|
66
|
+
* proxy log.
|
|
67
|
+
*
|
|
68
|
+
* ─── Ceilings (stated, not worked around) ────────────────────────────
|
|
69
|
+
*
|
|
70
|
+
* • NO FORCED TOOL CHOICE. `tool_choice` support is UNDOCUMENTED on
|
|
71
|
+
* this wire, so `carriesForcedToolChoice` is `false` and an agent
|
|
72
|
+
* using `.outputSchema(parser, { strategy: 'tool-forced' })` refuses
|
|
73
|
+
* at run start, naming this provider. Claiming an undocumented field
|
|
74
|
+
* works would turn a guarantee into a suggestion.
|
|
75
|
+
* • TOOL CALLING IS MODEL-DEPENDENT. `/foundry/list` reports
|
|
76
|
+
* `supportsToolCalling` per variant, but this adapter does not
|
|
77
|
+
* preflight-refuse on it — a wrong refusal is worse than a weak
|
|
78
|
+
* answer, the same stance `ollama()` takes on `/api/show`. Pick a
|
|
79
|
+
* tool-capable variant.
|
|
80
|
+
* • NO MULTI-MODAL. `LLMMessage.content` is a string. Same ceiling as
|
|
81
|
+
* every other adapter here.
|
|
82
|
+
* • NO PROMPT CACHING — resolves to the NoOp cache strategy.
|
|
83
|
+
* • NO STRUCTURED THINKING. The wire has no thinking field; a reasoning
|
|
84
|
+
* model's `<think>` tags ride the answer text untouched.
|
|
85
|
+
*/
|
|
86
|
+
import type { LLMCallHooks, LLMChunk, LLMProvider, LLMRequest, LLMResponse, WireRole } from '../types.js';
|
|
87
|
+
/**
|
|
88
|
+
* The two failures an on-device runtime actually has, told in words that
|
|
89
|
+
* contain the fix.
|
|
90
|
+
*
|
|
91
|
+
* Both are things the person at the keyboard can resolve in one command,
|
|
92
|
+
* which is exactly why they get a type instead of a wrapped
|
|
93
|
+
* `ECONNREFUSED` or a bare `404`. `reason` is the discriminator; the
|
|
94
|
+
* message already reads as instructions — and because Foundry Local's
|
|
95
|
+
* port changes per start, the unreachable message teaches the discovery
|
|
96
|
+
* command (`foundry server status`) alongside the start command.
|
|
97
|
+
*/
|
|
98
|
+
export declare class FoundryLocalUnavailableError extends Error {
|
|
99
|
+
readonly name = "FoundryLocalUnavailableError";
|
|
100
|
+
/** Which of the two situations this is. */
|
|
101
|
+
readonly reason: 'service-unreachable' | 'model-not-available';
|
|
102
|
+
/** The endpoint that was tried — the thing to check or change. */
|
|
103
|
+
readonly endpoint: string;
|
|
104
|
+
/** The model asked for. Absent when the service never answered at all. */
|
|
105
|
+
readonly model?: string;
|
|
106
|
+
/** Models this machine DOES have cached, when the service could tell us. */
|
|
107
|
+
readonly availableModels?: readonly string[];
|
|
108
|
+
constructor(init: {
|
|
109
|
+
reason: 'service-unreachable' | 'model-not-available';
|
|
110
|
+
endpoint: string;
|
|
111
|
+
model?: string;
|
|
112
|
+
availableModels?: readonly string[];
|
|
113
|
+
/**
|
|
114
|
+
* The 404 body did not speak this dialect, so "the model is missing" is
|
|
115
|
+
* a guess and the endpoint deserves naming too. Shapes the MESSAGE only;
|
|
116
|
+
* `reason` stays the discriminator consumers branch on.
|
|
117
|
+
*/
|
|
118
|
+
routeUnconfirmed?: boolean;
|
|
119
|
+
cause?: unknown;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export interface FoundryLocalProviderOptions {
|
|
123
|
+
/**
|
|
124
|
+
* Where the Foundry Local service is listening — the ROOT url; this
|
|
125
|
+
* adapter appends `/v1/chat/completions`, `/foundry/list` and
|
|
126
|
+
* `/openai/models` itself. A URL ending in `/v1` is accepted and
|
|
127
|
+
* trimmed (the same courtesy `ollama()` extends to its 8.0.0 configs),
|
|
128
|
+
* and a bare `host:port` gets `http://`.
|
|
129
|
+
*
|
|
130
|
+
* Defaults to `FOUNDRY_LOCAL_ENDPOINT` when set, then
|
|
131
|
+
* `FOUNDRY_LOCAL_BASE_URL` — the second spelling is honored because
|
|
132
|
+
* our own demo taught it, and a config written for that demo should
|
|
133
|
+
* keep working — otherwise `http://localhost:5272`. Know that the
|
|
134
|
+
* default is only the docs' example port: Foundry Local picks a NEW
|
|
135
|
+
* port on every `foundry server start` unless one was pinned with
|
|
136
|
+
* `--port`, and `foundry server status` prints the live URL.
|
|
137
|
+
*
|
|
138
|
+
* A BLANK value anywhere in that chain — `ENV FOUNDRY_LOCAL_ENDPOINT=`
|
|
139
|
+
* in a Dockerfile, an empty compose value, `manager.urls = ['']` —
|
|
140
|
+
* counts as unset, not as the URL `http:`. The next candidate gets its
|
|
141
|
+
* turn.
|
|
142
|
+
*/
|
|
143
|
+
readonly endpoint?: string;
|
|
144
|
+
/**
|
|
145
|
+
* A `foundry-local-sdk` `FoundryLocalManager`, duck-typed — this
|
|
146
|
+
* package never imports the SDK. When given, `manager.urls[0]` (the
|
|
147
|
+
* manager's discovered service URL) wins over the env vars and the
|
|
148
|
+
* default, so a consumer already using the SDK for model management
|
|
149
|
+
* gets the REAL dynamic port for free. An explicit `endpoint` still
|
|
150
|
+
* beats it — the most specific word wins.
|
|
151
|
+
*/
|
|
152
|
+
readonly manager?: {
|
|
153
|
+
readonly urls?: readonly string[];
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Model used when `LLMRequest.model` is the `'foundry-local'`
|
|
157
|
+
* shorthand. Prefer the positional form: `foundryLocal('qwen2.5-0.5b')`.
|
|
158
|
+
*/
|
|
159
|
+
readonly defaultModel?: string;
|
|
160
|
+
/** Default token cap when the request doesn't set one. Maps to `max_tokens`. */
|
|
161
|
+
readonly defaultMaxTokens?: number;
|
|
162
|
+
/**
|
|
163
|
+
* How long to wait for the service to ANSWER, in ms. Default 10000.
|
|
164
|
+
*
|
|
165
|
+
* This bounds the wait for response headers, NOT generation: a laptop
|
|
166
|
+
* model may take minutes to finish a long answer and that is fine.
|
|
167
|
+
* What it prevents is the failure this whole file exists to avoid — a
|
|
168
|
+
* run that hangs because nothing is listening. Stopping a call that is
|
|
169
|
+
* already streaming is the caller's `AbortSignal`'s job, not this
|
|
170
|
+
* one's — and that signal is honored for the WHOLE call, body included.
|
|
171
|
+
*/
|
|
172
|
+
readonly timeoutMs?: number;
|
|
173
|
+
/** @internal Custom fetch implementation for tests. */
|
|
174
|
+
readonly _fetch?: typeof fetch;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Build an `LLMProvider` backed by an on-device Foundry Local service.
|
|
178
|
+
*
|
|
179
|
+
* Free, offline, no API key. The rung between `mock()` and a paid API on
|
|
180
|
+
* a machine where Foundry Local is the local runtime — and the agent
|
|
181
|
+
* code above it does not change between the three.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* import { Agent } from 'agentfootprint';
|
|
185
|
+
* import { foundryLocal } from 'agentfootprint/providers';
|
|
186
|
+
*
|
|
187
|
+
* const agent = Agent.create({
|
|
188
|
+
* provider: foundryLocal('qwen2.5-0.5b'),
|
|
189
|
+
* model: 'qwen2.5-0.5b',
|
|
190
|
+
* }).build();
|
|
191
|
+
*
|
|
192
|
+
* @example // the service on its real (dynamic) port, via the SDK's manager
|
|
193
|
+
* foundryLocal('phi-3.5-mini', { manager });
|
|
194
|
+
*
|
|
195
|
+
* @example // a pinned port on another machine
|
|
196
|
+
* foundryLocal('qwen2.5-0.5b', { endpoint: 'http://192.168.1.20:5272' });
|
|
197
|
+
*/
|
|
198
|
+
export declare function foundryLocal(model: string, options?: FoundryLocalProviderOptions): LLMProvider;
|
|
199
|
+
/**
|
|
200
|
+
* Object form, matching the shape every sibling factory accepts.
|
|
201
|
+
* `defaultModel` names the model; everything else keeps its meaning.
|
|
202
|
+
*/
|
|
203
|
+
export declare function foundryLocal(options?: FoundryLocalProviderOptions): LLMProvider;
|
|
204
|
+
/**
|
|
205
|
+
* Class form for consumers who prefer `new FoundryLocalProvider(...)`.
|
|
206
|
+
*/
|
|
207
|
+
export declare class FoundryLocalProvider implements LLMProvider {
|
|
208
|
+
readonly name = "foundry-local";
|
|
209
|
+
readonly carriesInMessages: readonly WireRole[];
|
|
210
|
+
readonly carriesForcedToolChoice = false;
|
|
211
|
+
private readonly inner;
|
|
212
|
+
constructor(model?: string | FoundryLocalProviderOptions, options?: FoundryLocalProviderOptions);
|
|
213
|
+
complete(req: LLMRequest, hooks?: LLMCallHooks): Promise<LLMResponse>;
|
|
214
|
+
stream(req: LLMRequest, hooks?: LLMCallHooks): AsyncIterable<LLMChunk>;
|
|
215
|
+
}
|