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.
Files changed (66) hide show
  1. package/CHANGELOG.md +160 -0
  2. package/dist/adapters/identity/azure.js +306 -0
  3. package/dist/adapters/identity/azure.js.map +1 -0
  4. package/dist/adapters/llm/FoundryLocalProvider.js +992 -0
  5. package/dist/adapters/llm/FoundryLocalProvider.js.map +1 -0
  6. package/dist/adapters/llm/FoundryProvider.js +273 -0
  7. package/dist/adapters/llm/FoundryProvider.js.map +1 -0
  8. package/dist/adapters/llm/OllamaProvider.js +171 -12
  9. package/dist/adapters/llm/OllamaProvider.js.map +1 -1
  10. package/dist/adapters/llm/OpenAIProvider.js +117 -5
  11. package/dist/adapters/llm/OpenAIProvider.js.map +1 -1
  12. package/dist/adapters/llm/createProvider.js +143 -10
  13. package/dist/adapters/llm/createProvider.js.map +1 -1
  14. package/dist/adapters/types.js.map +1 -1
  15. package/dist/esm/adapters/identity/azure.d.ts +188 -0
  16. package/dist/esm/adapters/identity/azure.js +302 -0
  17. package/dist/esm/adapters/identity/azure.js.map +1 -0
  18. package/dist/esm/adapters/llm/FoundryLocalProvider.d.ts +215 -0
  19. package/dist/esm/adapters/llm/FoundryLocalProvider.js +986 -0
  20. package/dist/esm/adapters/llm/FoundryLocalProvider.js.map +1 -0
  21. package/dist/esm/adapters/llm/FoundryProvider.d.ts +178 -0
  22. package/dist/esm/adapters/llm/FoundryProvider.js +268 -0
  23. package/dist/esm/adapters/llm/FoundryProvider.js.map +1 -0
  24. package/dist/esm/adapters/llm/OllamaProvider.js +171 -12
  25. package/dist/esm/adapters/llm/OllamaProvider.js.map +1 -1
  26. package/dist/esm/adapters/llm/OpenAIProvider.d.ts +111 -0
  27. package/dist/esm/adapters/llm/OpenAIProvider.js +115 -4
  28. package/dist/esm/adapters/llm/OpenAIProvider.js.map +1 -1
  29. package/dist/esm/adapters/llm/createProvider.d.ts +48 -12
  30. package/dist/esm/adapters/llm/createProvider.js +143 -10
  31. package/dist/esm/adapters/llm/createProvider.js.map +1 -1
  32. package/dist/esm/adapters/types.d.ts +4 -2
  33. package/dist/esm/adapters/types.js.map +1 -1
  34. package/dist/esm/identity.d.ts +1 -0
  35. package/dist/esm/identity.js +9 -0
  36. package/dist/esm/identity.js.map +1 -1
  37. package/dist/esm/index.d.ts +1 -1
  38. package/dist/esm/index.js.map +1 -1
  39. package/dist/esm/providers.d.ts +5 -0
  40. package/dist/esm/providers.js +14 -0
  41. package/dist/esm/providers.js.map +1 -1
  42. package/dist/identity.js +14 -1
  43. package/dist/identity.js.map +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/providers.js +20 -1
  46. package/dist/providers.js.map +1 -1
  47. package/dist/types/adapters/identity/azure.d.ts +189 -0
  48. package/dist/types/adapters/identity/azure.d.ts.map +1 -0
  49. package/dist/types/adapters/llm/FoundryLocalProvider.d.ts +216 -0
  50. package/dist/types/adapters/llm/FoundryLocalProvider.d.ts.map +1 -0
  51. package/dist/types/adapters/llm/FoundryProvider.d.ts +179 -0
  52. package/dist/types/adapters/llm/FoundryProvider.d.ts.map +1 -0
  53. package/dist/types/adapters/llm/OllamaProvider.d.ts.map +1 -1
  54. package/dist/types/adapters/llm/OpenAIProvider.d.ts +111 -0
  55. package/dist/types/adapters/llm/OpenAIProvider.d.ts.map +1 -1
  56. package/dist/types/adapters/llm/createProvider.d.ts +48 -12
  57. package/dist/types/adapters/llm/createProvider.d.ts.map +1 -1
  58. package/dist/types/adapters/types.d.ts +4 -2
  59. package/dist/types/adapters/types.d.ts.map +1 -1
  60. package/dist/types/identity.d.ts +1 -0
  61. package/dist/types/identity.d.ts.map +1 -1
  62. package/dist/types/index.d.ts +1 -1
  63. package/dist/types/index.d.ts.map +1 -1
  64. package/dist/types/providers.d.ts +5 -0
  65. package/dist/types/providers.d.ts.map +1 -1
  66. 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
+ }