@oharness/engine 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/dist/agent/agent.d.ts +196 -0
- package/dist/agent/agent.js +758 -0
- package/dist/agent/agent.js.map +1 -0
- package/dist/agent/compaction.d.ts +68 -0
- package/dist/agent/compaction.js +179 -0
- package/dist/agent/compaction.js.map +1 -0
- package/dist/agent/hooks.d.ts +77 -0
- package/dist/agent/hooks.js +152 -0
- package/dist/agent/hooks.js.map +1 -0
- package/dist/agent/memory.d.ts +45 -0
- package/dist/agent/memory.js +147 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/overflow.d.ts +35 -0
- package/dist/agent/overflow.js +69 -0
- package/dist/agent/overflow.js.map +1 -0
- package/dist/agent/permissions.d.ts +181 -0
- package/dist/agent/permissions.js +289 -0
- package/dist/agent/permissions.js.map +1 -0
- package/dist/agent/prompt.d.ts +25 -0
- package/dist/agent/prompt.js +63 -0
- package/dist/agent/prompt.js.map +1 -0
- package/dist/agent/session.d.ts +108 -0
- package/dist/agent/session.js +231 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/settings.d.ts +37 -0
- package/dist/agent/settings.js +113 -0
- package/dist/agent/settings.js.map +1 -0
- package/dist/agent/skills.d.ts +58 -0
- package/dist/agent/skills.js +225 -0
- package/dist/agent/skills.js.map +1 -0
- package/dist/agent/store.d.ts +308 -0
- package/dist/agent/store.js +596 -0
- package/dist/agent/store.js.map +1 -0
- package/dist/agent/subagent.d.ts +44 -0
- package/dist/agent/subagent.js +138 -0
- package/dist/agent/subagent.js.map +1 -0
- package/dist/agent/titles.d.ts +8 -0
- package/dist/agent/titles.js +23 -0
- package/dist/agent/titles.js.map +1 -0
- package/dist/agent/workspace.d.ts +60 -0
- package/dist/agent/workspace.js +156 -0
- package/dist/agent/workspace.js.map +1 -0
- package/dist/artifacts/publish.d.ts +94 -0
- package/dist/artifacts/publish.js +104 -0
- package/dist/artifacts/publish.js.map +1 -0
- package/dist/artifacts/store.d.ts +324 -0
- package/dist/artifacts/store.js +500 -0
- package/dist/artifacts/store.js.map +1 -0
- package/dist/auth/index.d.ts +13 -0
- package/dist/auth/index.js +10 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/manager.d.ts +83 -0
- package/dist/auth/manager.js +266 -0
- package/dist/auth/manager.js.map +1 -0
- package/dist/auth/oauth.d.ts +83 -0
- package/dist/auth/oauth.js +327 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/providers/account.d.ts +59 -0
- package/dist/auth/providers/account.js +282 -0
- package/dist/auth/providers/account.js.map +1 -0
- package/dist/auth/providers/anthropic.d.ts +24 -0
- package/dist/auth/providers/anthropic.js +209 -0
- package/dist/auth/providers/anthropic.js.map +1 -0
- package/dist/auth/providers/generic.d.ts +57 -0
- package/dist/auth/providers/generic.js +155 -0
- package/dist/auth/providers/generic.js.map +1 -0
- package/dist/auth/providers/official.d.ts +3 -0
- package/dist/auth/providers/official.js +90 -0
- package/dist/auth/providers/official.js.map +1 -0
- package/dist/auth/providers/ottoport.d.ts +3 -0
- package/dist/auth/providers/ottoport.js +60 -0
- package/dist/auth/providers/ottoport.js.map +1 -0
- package/dist/auth/store.d.ts +22 -0
- package/dist/auth/store.js +138 -0
- package/dist/auth/store.js.map +1 -0
- package/dist/auth/types.d.ts +167 -0
- package/dist/auth/types.js +16 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/config.d.ts +197 -0
- package/dist/config.js +195 -0
- package/dist/config.js.map +1 -0
- package/dist/core/errors.d.ts +46 -0
- package/dist/core/errors.js +83 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/tokens.d.ts +12 -0
- package/dist/core/tokens.js +56 -0
- package/dist/core/tokens.js.map +1 -0
- package/dist/core/types.d.ts +91 -0
- package/dist/core/types.js +49 -0
- package/dist/core/types.js.map +1 -0
- package/dist/harness.d.ts +302 -0
- package/dist/harness.js +695 -0
- package/dist/harness.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/client.d.ts +55 -0
- package/dist/mcp/client.js +199 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/manager.d.ts +77 -0
- package/dist/mcp/manager.js +325 -0
- package/dist/mcp/manager.js.map +1 -0
- package/dist/mcp/oauth.d.ts +63 -0
- package/dist/mcp/oauth.js +302 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/protocol.d.ts +130 -0
- package/dist/mcp/protocol.js +14 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/registry.d.ts +52 -0
- package/dist/mcp/registry.js +100 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/registry.json +293 -0
- package/dist/mcp/secrets.d.ts +20 -0
- package/dist/mcp/secrets.js +75 -0
- package/dist/mcp/secrets.js.map +1 -0
- package/dist/mcp/transport.d.ts +51 -0
- package/dist/mcp/transport.js +158 -0
- package/dist/mcp/transport.js.map +1 -0
- package/dist/paths.d.ts +37 -0
- package/dist/paths.js +63 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugins.d.ts +55 -0
- package/dist/plugins.js +201 -0
- package/dist/plugins.js.map +1 -0
- package/dist/providers/builtin.d.ts +23 -0
- package/dist/providers/builtin.js +141 -0
- package/dist/providers/builtin.js.map +1 -0
- package/dist/providers/catalog.d.ts +129 -0
- package/dist/providers/catalog.js +254 -0
- package/dist/providers/catalog.js.map +1 -0
- package/dist/providers/catalog.json +472 -0
- package/dist/providers/discovery.d.ts +16 -0
- package/dist/providers/discovery.js +161 -0
- package/dist/providers/discovery.js.map +1 -0
- package/dist/providers/http.d.ts +24 -0
- package/dist/providers/http.js +139 -0
- package/dist/providers/http.js.map +1 -0
- package/dist/providers/index.d.ts +10 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/piai.d.ts +124 -0
- package/dist/providers/piai.js +551 -0
- package/dist/providers/piai.js.map +1 -0
- package/dist/providers/registry.d.ts +65 -0
- package/dist/providers/registry.js +195 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/resilient.d.ts +46 -0
- package/dist/providers/resilient.js +117 -0
- package/dist/providers/resilient.js.map +1 -0
- package/dist/providers/types.d.ts +182 -0
- package/dist/providers/types.js +50 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/schedule/cron.d.ts +35 -0
- package/dist/schedule/cron.js +125 -0
- package/dist/schedule/cron.js.map +1 -0
- package/dist/schedule/scheduler.d.ts +87 -0
- package/dist/schedule/scheduler.js +237 -0
- package/dist/schedule/scheduler.js.map +1 -0
- package/dist/storage/kv.d.ts +56 -0
- package/dist/storage/kv.js +173 -0
- package/dist/storage/kv.js.map +1 -0
- package/dist/storage/migrate.d.ts +11 -0
- package/dist/storage/migrate.js +93 -0
- package/dist/storage/migrate.js.map +1 -0
- package/dist/storage/project.d.ts +9 -0
- package/dist/storage/project.js +57 -0
- package/dist/storage/project.js.map +1 -0
- package/dist/storage/records.d.ts +32 -0
- package/dist/storage/records.js +48 -0
- package/dist/storage/records.js.map +1 -0
- package/dist/sync/client.d.ts +79 -0
- package/dist/sync/client.js +118 -0
- package/dist/sync/client.js.map +1 -0
- package/dist/sync/mirror.d.ts +85 -0
- package/dist/sync/mirror.js +389 -0
- package/dist/sync/mirror.js.map +1 -0
- package/dist/sync/protocol.d.ts +67 -0
- package/dist/sync/protocol.js +16 -0
- package/dist/sync/protocol.js.map +1 -0
- package/dist/sync/settings.d.ts +56 -0
- package/dist/sync/settings.js +60 -0
- package/dist/sync/settings.js.map +1 -0
- package/dist/tools/builtin/artifact.d.ts +16 -0
- package/dist/tools/builtin/artifact.js +134 -0
- package/dist/tools/builtin/artifact.js.map +1 -0
- package/dist/tools/builtin/bash.d.ts +3 -0
- package/dist/tools/builtin/bash.js +142 -0
- package/dist/tools/builtin/bash.js.map +1 -0
- package/dist/tools/builtin/fs.d.ts +19 -0
- package/dist/tools/builtin/fs.js +280 -0
- package/dist/tools/builtin/fs.js.map +1 -0
- package/dist/tools/builtin/index.d.ts +22 -0
- package/dist/tools/builtin/index.js +42 -0
- package/dist/tools/builtin/index.js.map +1 -0
- package/dist/tools/builtin/mcp-resource.d.ts +3 -0
- package/dist/tools/builtin/mcp-resource.js +220 -0
- package/dist/tools/builtin/mcp-resource.js.map +1 -0
- package/dist/tools/builtin/memory.d.ts +10 -0
- package/dist/tools/builtin/memory.js +76 -0
- package/dist/tools/builtin/memory.js.map +1 -0
- package/dist/tools/builtin/recall.d.ts +13 -0
- package/dist/tools/builtin/recall.js +163 -0
- package/dist/tools/builtin/recall.js.map +1 -0
- package/dist/tools/builtin/search.d.ts +3 -0
- package/dist/tools/builtin/search.js +240 -0
- package/dist/tools/builtin/search.js.map +1 -0
- package/dist/tools/builtin/skill.d.ts +10 -0
- package/dist/tools/builtin/skill.js +46 -0
- package/dist/tools/builtin/skill.js.map +1 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +68 -0
- package/dist/tools/builtin/task.js.map +1 -0
- package/dist/tools/builtin/todo.d.ts +20 -0
- package/dist/tools/builtin/todo.js +63 -0
- package/dist/tools/builtin/todo.js.map +1 -0
- package/dist/tools/define.d.ts +19 -0
- package/dist/tools/define.js +39 -0
- package/dist/tools/define.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +5 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/registry.d.ts +39 -0
- package/dist/tools/registry.js +101 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +112 -0
- package/dist/tools/types.js +22 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/util/id.d.ts +9 -0
- package/dist/util/id.js +19 -0
- package/dist/util/id.js.map +1 -0
- package/dist/util/json.d.ts +25 -0
- package/dist/util/json.js +100 -0
- package/dist/util/json.js.map +1 -0
- package/dist/util/jwt.d.ts +74 -0
- package/dist/util/jwt.js +177 -0
- package/dist/util/jwt.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.js +59 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/queue.d.ts +26 -0
- package/dist/util/queue.js +106 -0
- package/dist/util/queue.js.map +1 -0
- package/dist/util/schema.d.ts +17 -0
- package/dist/util/schema.js +98 -0
- package/dist/util/schema.js.map +1 -0
- package/dist/util/shell.d.ts +20 -0
- package/dist/util/shell.js +24 -0
- package/dist/util/shell.js.map +1 -0
- package/official.json +4 -0
- package/package.json +38 -0
- package/skills/artifact-authoring/SKILL.md +81 -0
- package/skills/canvas/SKILL.md +85 -0
- package/skills/skill-authoring/SKILL.md +85 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { ConfigError } from "../core/errors.js";
|
|
2
|
+
import { PI_APIS, PiAiProvider } from "./piai.js";
|
|
3
|
+
import { ResilientProvider } from "./resilient.js";
|
|
4
|
+
/**
|
|
5
|
+
* Holds providers and model specs, and resolves the aliases used by `/model`.
|
|
6
|
+
*
|
|
7
|
+
* Model switching lives here rather than in the agent: because conversation
|
|
8
|
+
* history is stored provider-neutrally, changing models is nothing more than
|
|
9
|
+
* resolving a different spec for the next request.
|
|
10
|
+
*/
|
|
11
|
+
export class ProviderRegistry {
|
|
12
|
+
providers = new Map();
|
|
13
|
+
models = new Map();
|
|
14
|
+
catalogProviders = {};
|
|
15
|
+
/**
|
|
16
|
+
* Build a registry from a catalog.
|
|
17
|
+
*
|
|
18
|
+
* Every provider comes from data — which protocol it speaks, where it lives,
|
|
19
|
+
* which env var holds its key — so adding a vendor never requires a code
|
|
20
|
+
* change, only a catalog entry.
|
|
21
|
+
*/
|
|
22
|
+
static fromCatalog(catalog, opts = {}) {
|
|
23
|
+
const registry = new ProviderRegistry();
|
|
24
|
+
registry.catalogProviders = catalog.providers;
|
|
25
|
+
for (const provider of Object.values(catalog.providers)) {
|
|
26
|
+
// Every provider gets mid-stream resilience: a dropped connection part
|
|
27
|
+
// way through a long answer should not discard the whole turn.
|
|
28
|
+
registry.registerProvider(new ResilientProvider(buildProvider(provider, { ...opts.creds?.[provider.id], auth: opts.auth })));
|
|
29
|
+
}
|
|
30
|
+
for (const model of catalog.models)
|
|
31
|
+
registry.registerModel(model);
|
|
32
|
+
return registry;
|
|
33
|
+
}
|
|
34
|
+
/** The catalog entry behind a provider, for `/models` and login hints. */
|
|
35
|
+
catalogEntry(id) {
|
|
36
|
+
return this.catalogProviders[id];
|
|
37
|
+
}
|
|
38
|
+
listCatalogProviders() {
|
|
39
|
+
return Object.values(this.catalogProviders);
|
|
40
|
+
}
|
|
41
|
+
registerProvider(provider) {
|
|
42
|
+
this.providers.set(provider.id, provider);
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
/** Register an OpenAI-compatible vendor under its own provider id. */
|
|
46
|
+
registerOpenAiCompatible(opts) {
|
|
47
|
+
return this.registerProvider(new PiAiProvider({ api: "openai-completions", ...opts }));
|
|
48
|
+
}
|
|
49
|
+
/** Register (or replace) a provider from a catalog entry. */
|
|
50
|
+
registerCatalogProvider(entry, creds = {}) {
|
|
51
|
+
this.catalogProviders[entry.id] = entry;
|
|
52
|
+
return this.registerProvider(new ResilientProvider(buildProvider(entry, creds)));
|
|
53
|
+
}
|
|
54
|
+
registerModel(spec) {
|
|
55
|
+
this.models.set(spec.id, spec);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
getProvider(id) {
|
|
59
|
+
const provider = this.providers.get(id);
|
|
60
|
+
if (!provider)
|
|
61
|
+
throw new ConfigError(`unknown provider: ${id}`);
|
|
62
|
+
return provider;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolve an alias (`sonnet`), a wire model id (`claude-sonnet-5`), or a
|
|
66
|
+
* fully qualified `provider/model` reference.
|
|
67
|
+
*/
|
|
68
|
+
resolve(ref) {
|
|
69
|
+
const direct = this.models.get(ref);
|
|
70
|
+
if (direct)
|
|
71
|
+
return direct;
|
|
72
|
+
const slash = ref.indexOf("/");
|
|
73
|
+
if (slash !== -1) {
|
|
74
|
+
const providerId = ref.slice(0, slash);
|
|
75
|
+
const modelId = ref.slice(slash + 1);
|
|
76
|
+
const known = [...this.models.values()].find((m) => m.provider === providerId && (m.model === modelId || m.id === modelId));
|
|
77
|
+
if (known)
|
|
78
|
+
return known;
|
|
79
|
+
// Allow arbitrary models on a registered provider without pre-declaring
|
|
80
|
+
// them — useful for local servers and freshly released models.
|
|
81
|
+
if (this.providers.has(providerId)) {
|
|
82
|
+
return unknownModelSpec(providerId, modelId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const byWireId = [...this.models.values()].find((m) => m.model === ref);
|
|
86
|
+
if (byWireId)
|
|
87
|
+
return byWireId;
|
|
88
|
+
throw new ConfigError(`unknown model: ${ref}. Known: ${this.listModels().map((m) => m.id).join(", ")}`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Models worth showing.
|
|
92
|
+
*
|
|
93
|
+
* Aliases are excluded: they resolve, but they are the same model under a
|
|
94
|
+
* shorter name, and listing both turns a discovered roster of 300 into 600.
|
|
95
|
+
*/
|
|
96
|
+
listModels(opts = {}) {
|
|
97
|
+
let all = [...this.models.values()];
|
|
98
|
+
if (!opts.includeAliases)
|
|
99
|
+
all = all.filter((model) => !model.aliasOf);
|
|
100
|
+
if (!opts.configuredOnly)
|
|
101
|
+
return all;
|
|
102
|
+
return all.filter((m) => this.providers.get(m.provider)?.isConfigured());
|
|
103
|
+
}
|
|
104
|
+
listProviders() {
|
|
105
|
+
return [...this.providers.values()];
|
|
106
|
+
}
|
|
107
|
+
hasModel(id) {
|
|
108
|
+
return this.models.has(id);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Ask providers that support it which models the account can actually use,
|
|
112
|
+
* and merge the results into the catalog.
|
|
113
|
+
*
|
|
114
|
+
* Subscription plans gate models by entitlement, so a static catalog will
|
|
115
|
+
* happily offer something the account cannot call. Returns the ids added or
|
|
116
|
+
* updated.
|
|
117
|
+
*/
|
|
118
|
+
async refreshModels(providerId) {
|
|
119
|
+
const targets = providerId
|
|
120
|
+
? [this.providers.get(providerId)].filter((p) => p !== undefined)
|
|
121
|
+
: [...this.providers.values()];
|
|
122
|
+
const changed = [];
|
|
123
|
+
for (const provider of targets) {
|
|
124
|
+
if (!provider.listModels)
|
|
125
|
+
continue;
|
|
126
|
+
try {
|
|
127
|
+
for (const spec of await provider.listModels()) {
|
|
128
|
+
this.registerModel(spec);
|
|
129
|
+
changed.push(spec);
|
|
130
|
+
// Also expose a bare alias so `/model kimi-k3` works and not only
|
|
131
|
+
// `/model moonshot/kimi-k3`. First provider to claim a name keeps
|
|
132
|
+
// it — silently reassigning an alias across vendors would change
|
|
133
|
+
// which model a saved config resolves to.
|
|
134
|
+
if (!this.models.has(spec.model)) {
|
|
135
|
+
this.registerModel({ ...spec, id: spec.model, aliasOf: spec.id });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// A provider that cannot enumerate its models is not fatal; the
|
|
141
|
+
// catalog entries still work.
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return changed;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Conservative defaults for a model we were not told about: assume tools work
|
|
150
|
+
* (the harness is useless otherwise) but assume nothing about reasoning
|
|
151
|
+
* controls, since guessing wrong there is a hard 400.
|
|
152
|
+
*/
|
|
153
|
+
function unknownModelSpec(providerId, modelId) {
|
|
154
|
+
return {
|
|
155
|
+
id: `${providerId}/${modelId}`,
|
|
156
|
+
provider: providerId,
|
|
157
|
+
model: modelId,
|
|
158
|
+
displayName: modelId,
|
|
159
|
+
contextWindow: 128_000,
|
|
160
|
+
maxOutputTokens: 8_192,
|
|
161
|
+
supportsTools: true,
|
|
162
|
+
supportsImages: false,
|
|
163
|
+
supportsParallelToolCalls: true,
|
|
164
|
+
supportsPromptCache: false,
|
|
165
|
+
capabilitiesAssumed: true,
|
|
166
|
+
thinkingMode: "none",
|
|
167
|
+
effortLevels: [],
|
|
168
|
+
supportsSampling: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/** Construct the adapter for a catalog entry's protocol. */
|
|
172
|
+
function buildProvider(entry, creds) {
|
|
173
|
+
const api = PI_APIS[entry.protocol];
|
|
174
|
+
if (!api) {
|
|
175
|
+
throw new ConfigError(`provider ${entry.id} declares unknown protocol ${entry.protocol}`);
|
|
176
|
+
}
|
|
177
|
+
return new PiAiProvider({
|
|
178
|
+
baseUrl: entry.baseUrl,
|
|
179
|
+
...creds,
|
|
180
|
+
// Merge rather than replace: a config adding one custom header must not
|
|
181
|
+
// drop the attribution/version headers the vendor requires.
|
|
182
|
+
headers: { ...entry.headers, ...creds.headers },
|
|
183
|
+
id: entry.id,
|
|
184
|
+
name: entry.label,
|
|
185
|
+
api,
|
|
186
|
+
apiKeyEnv: entry.envVars[0],
|
|
187
|
+
includeUsage: entry.includeUsage,
|
|
188
|
+
idempotencyKey: entry.idempotencyKey,
|
|
189
|
+
maxOutputTokens: entry.maxOutputTokens,
|
|
190
|
+
discoveryFormat: entry.discovery,
|
|
191
|
+
// An entry with no env vars is a local server that needs no key.
|
|
192
|
+
requiresKey: entry.envVars.length > 0,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAMnD;;;;;;GAMG;AACH,MAAM,OAAO,gBAAgB;IACV,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IACxC,MAAM,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC/C,gBAAgB,GAAoC,EAAE,CAAC;IAE/D;;;;;;OAMG;IACH,MAAM,CAAC,WAAW,CAChB,OAAgB,EAChB,OAA6E,EAAE;QAE/E,MAAM,QAAQ,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACxC,QAAQ,CAAC,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC;QAE9C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;YACxD,uEAAuE;YACvE,+DAA+D;YAC/D,QAAQ,CAAC,gBAAgB,CACvB,IAAI,iBAAiB,CACnB,aAAa,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAC3E,CACF,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM;YAAE,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAClE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,0EAA0E;IAC1E,YAAY,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,oBAAoB;QAClB,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC;IAED,gBAAgB,CAAC,QAAkB;QACjC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sEAAsE;IACtE,wBAAwB,CAAC,IAAuD;QAC9E,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,YAAY,CAAC,EAAE,GAAG,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,6DAA6D;IAC7D,uBAAuB,CAAC,KAAsB,EAAE,QAA6B,EAAE;QAC7E,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC;QACxC,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,iBAAiB,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACnF,CAAC;IAED,aAAa,CAAC,IAAe;QAC3B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,WAAW,CAAC,EAAU;QACpB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,WAAW,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,GAAW;QACjB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;YACjB,MAAM,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YACvC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACrC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC1C,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,IAAI,CAAC,CAAC,EAAE,KAAK,OAAO,CAAC,CAC9E,CAAC;YACF,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;YACxB,wEAAwE;YACxE,+DAA+D;YAC/D,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBACnC,OAAO,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;QAED,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC;QACxE,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAE9B,MAAM,IAAI,WAAW,CACnB,kBAAkB,GAAG,YAAY,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACjF,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,OAA+D,EAAE;QAC1E,IAAI,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,cAAc;YAAE,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,CAAC,cAAc;YAAE,OAAO,GAAG,CAAC;QACrC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,aAAa;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,QAAQ,CAAC,EAAU;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa,CAAC,UAAmB;QACrC,MAAM,OAAO,GAAG,UAAU;YACxB,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAiB,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;YAChF,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;QAEjC,MAAM,OAAO,GAAgB,EAAE,CAAC;QAChC,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ,CAAC,UAAU;gBAAE,SAAS;YACnC,IAAI,CAAC;gBACH,KAAK,MAAM,IAAI,IAAI,MAAM,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC;oBAC/C,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;oBACzB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBAEnB,kEAAkE;oBAClE,kEAAkE;oBAClE,iEAAiE;oBACjE,0CAA0C;oBAC1C,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;wBACjC,IAAI,CAAC,aAAa,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;oBACpE,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,gEAAgE;gBAChE,8BAA8B;gBAC9B,SAAS;YACX,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,UAAkB,EAAE,OAAe;IAC3D,OAAO;QACL,EAAE,EAAE,GAAG,UAAU,IAAI,OAAO,EAAE;QAC9B,QAAQ,EAAE,UAAU;QACpB,KAAK,EAAE,OAAO;QACd,WAAW,EAAE,OAAO;QACpB,aAAa,EAAE,OAAO;QACtB,eAAe,EAAE,KAAK;QACtB,aAAa,EAAE,IAAI;QACnB,cAAc,EAAE,KAAK;QACrB,yBAAyB,EAAE,IAAI;QAC/B,mBAAmB,EAAE,KAAK;QAC1B,mBAAmB,EAAE,IAAI;QACzB,YAAY,EAAE,MAAM;QACpB,YAAY,EAAE,EAAE;QAChB,gBAAgB,EAAE,IAAI;KACvB,CAAC;AACJ,CAAC;AAED,4DAA4D;AAC5D,SAAS,aAAa,CAAC,KAAsB,EAAE,KAA0B;IACvE,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,WAAW,CAAC,YAAY,KAAK,CAAC,EAAE,8BAA8B,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,OAAO,IAAI,YAAY,CAAC;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,KAAK;QACR,wEAAwE;QACxE,4DAA4D;QAC5D,OAAO,EAAE,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE;QAC/C,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,KAAK;QACjB,GAAG;QACH,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC3B,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,eAAe,EAAE,KAAK,CAAC,SAAS;QAChC,iEAAiE;QACjE,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;KACtC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Logger } from "../util/logger.ts";
|
|
2
|
+
import type { GenerateRequest, ModelSpec, Provider, StreamEvent } from "./types.ts";
|
|
3
|
+
export interface ResilientOptions {
|
|
4
|
+
/** Restarts allowed when the stream dies before emitting anything. */
|
|
5
|
+
maxRestarts?: number;
|
|
6
|
+
logger?: Logger;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Wrap a provider so a broken stream does not necessarily lose the turn.
|
|
10
|
+
*
|
|
11
|
+
* `postJson` already retries the initial request, but that only covers
|
|
12
|
+
* failures before the response headers. A stream that dies *mid-generation* —
|
|
13
|
+
* a dropped connection, a proxy timeout on a long answer — used to throw and
|
|
14
|
+
* take every token already produced with it.
|
|
15
|
+
*
|
|
16
|
+
* The split matters:
|
|
17
|
+
*
|
|
18
|
+
* - **Nothing emitted yet** — nothing to lose, so restart the request. The
|
|
19
|
+
* model has not committed to an answer and a retry is free apart from input
|
|
20
|
+
* tokens.
|
|
21
|
+
* - **Something emitted** — restarting would duplicate the text already shown
|
|
22
|
+
* and re-bill the output. Keep the partial turn and end it with a non-final
|
|
23
|
+
* stop reason, so the agent loop can decide whether to continue.
|
|
24
|
+
*
|
|
25
|
+
* Restarting after partial output is the tempting option and the wrong one: it
|
|
26
|
+
* produces two half-answers glued together, and the user has already seen the
|
|
27
|
+
* first one.
|
|
28
|
+
*/
|
|
29
|
+
export declare class ResilientProvider implements Provider {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
private readonly inner;
|
|
33
|
+
private readonly opts;
|
|
34
|
+
private readonly logger;
|
|
35
|
+
/**
|
|
36
|
+
* Only defined when the wrapped provider supports discovery — callers test
|
|
37
|
+
* for its presence, so defining it unconditionally would make every provider
|
|
38
|
+
* claim it can enumerate models.
|
|
39
|
+
*/
|
|
40
|
+
readonly listModels?: () => Promise<ModelSpec[]>;
|
|
41
|
+
constructor(inner: Provider, opts?: ResilientOptions);
|
|
42
|
+
isConfigured(): boolean;
|
|
43
|
+
/** The wrapped adapter, for diagnostics and tests. */
|
|
44
|
+
unwrap(): Provider;
|
|
45
|
+
stream(request: GenerateRequest): AsyncGenerator<StreamEvent, void, void>;
|
|
46
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { ProviderError, errorMessage } from "../core/errors.js";
|
|
2
|
+
import { emptyUsage } from "../core/types.js";
|
|
3
|
+
import { createLogger } from "../util/logger.js";
|
|
4
|
+
const DEFAULT_MAX_RESTARTS = 2;
|
|
5
|
+
/**
|
|
6
|
+
* Wrap a provider so a broken stream does not necessarily lose the turn.
|
|
7
|
+
*
|
|
8
|
+
* `postJson` already retries the initial request, but that only covers
|
|
9
|
+
* failures before the response headers. A stream that dies *mid-generation* —
|
|
10
|
+
* a dropped connection, a proxy timeout on a long answer — used to throw and
|
|
11
|
+
* take every token already produced with it.
|
|
12
|
+
*
|
|
13
|
+
* The split matters:
|
|
14
|
+
*
|
|
15
|
+
* - **Nothing emitted yet** — nothing to lose, so restart the request. The
|
|
16
|
+
* model has not committed to an answer and a retry is free apart from input
|
|
17
|
+
* tokens.
|
|
18
|
+
* - **Something emitted** — restarting would duplicate the text already shown
|
|
19
|
+
* and re-bill the output. Keep the partial turn and end it with a non-final
|
|
20
|
+
* stop reason, so the agent loop can decide whether to continue.
|
|
21
|
+
*
|
|
22
|
+
* Restarting after partial output is the tempting option and the wrong one: it
|
|
23
|
+
* produces two half-answers glued together, and the user has already seen the
|
|
24
|
+
* first one.
|
|
25
|
+
*/
|
|
26
|
+
export class ResilientProvider {
|
|
27
|
+
id;
|
|
28
|
+
name;
|
|
29
|
+
inner;
|
|
30
|
+
opts;
|
|
31
|
+
logger;
|
|
32
|
+
/**
|
|
33
|
+
* Only defined when the wrapped provider supports discovery — callers test
|
|
34
|
+
* for its presence, so defining it unconditionally would make every provider
|
|
35
|
+
* claim it can enumerate models.
|
|
36
|
+
*/
|
|
37
|
+
listModels;
|
|
38
|
+
constructor(inner, opts = {}) {
|
|
39
|
+
this.inner = inner;
|
|
40
|
+
this.opts = opts;
|
|
41
|
+
this.id = inner.id;
|
|
42
|
+
this.name = inner.name;
|
|
43
|
+
this.logger = (opts.logger ?? createLogger("provider")).child(`resilient:${inner.id}`);
|
|
44
|
+
if (inner.listModels)
|
|
45
|
+
this.listModels = () => inner.listModels();
|
|
46
|
+
}
|
|
47
|
+
isConfigured() {
|
|
48
|
+
return this.inner.isConfigured();
|
|
49
|
+
}
|
|
50
|
+
/** The wrapped adapter, for diagnostics and tests. */
|
|
51
|
+
unwrap() {
|
|
52
|
+
return this.inner;
|
|
53
|
+
}
|
|
54
|
+
async *stream(request) {
|
|
55
|
+
const maxRestarts = this.opts.maxRestarts ?? DEFAULT_MAX_RESTARTS;
|
|
56
|
+
for (let attempt = 0;; attempt++) {
|
|
57
|
+
let emitted = false;
|
|
58
|
+
let ended = false;
|
|
59
|
+
try {
|
|
60
|
+
for await (const event of this.inner.stream(request)) {
|
|
61
|
+
// "Emitted" means content the caller can already see. A `start`
|
|
62
|
+
// event carries nothing, so a failure right after it is still safe
|
|
63
|
+
// to restart.
|
|
64
|
+
if (event.type === "text_delta" ||
|
|
65
|
+
event.type === "thinking_delta" ||
|
|
66
|
+
event.type === "tool_call_start" ||
|
|
67
|
+
event.type === "tool_call_delta" ||
|
|
68
|
+
event.type === "tool_call_end") {
|
|
69
|
+
emitted = true;
|
|
70
|
+
}
|
|
71
|
+
if (event.type === "end")
|
|
72
|
+
ended = true;
|
|
73
|
+
yield event;
|
|
74
|
+
}
|
|
75
|
+
// A stream that finished without an `end` event was cut short between
|
|
76
|
+
// the last chunk and the terminator.
|
|
77
|
+
if (!ended) {
|
|
78
|
+
this.logger.warn("stream closed without a terminator", { attempt });
|
|
79
|
+
yield {
|
|
80
|
+
type: "end",
|
|
81
|
+
stopReason: emitted ? "error" : "end_turn",
|
|
82
|
+
usage: emptyUsage(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
// A caller-initiated abort is intentional; never retry it.
|
|
89
|
+
if (request.signal?.aborted)
|
|
90
|
+
throw err;
|
|
91
|
+
const retryable = !(err instanceof ProviderError) || err.retryable;
|
|
92
|
+
if (emitted) {
|
|
93
|
+
// Keep what the user already saw rather than replaying it.
|
|
94
|
+
this.logger.warn("stream failed after partial output; keeping the partial turn", {
|
|
95
|
+
error: errorMessage(err),
|
|
96
|
+
});
|
|
97
|
+
yield { type: "end", stopReason: "error", usage: emptyUsage() };
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!retryable || attempt >= maxRestarts)
|
|
101
|
+
throw err;
|
|
102
|
+
this.logger.warn("stream failed before any output; restarting", {
|
|
103
|
+
attempt: attempt + 1,
|
|
104
|
+
error: errorMessage(err),
|
|
105
|
+
});
|
|
106
|
+
await sleep(backoffMs(attempt));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function backoffMs(attempt) {
|
|
112
|
+
return Math.min(1_000 * 2 ** attempt, 8_000) + Math.random() * 250;
|
|
113
|
+
}
|
|
114
|
+
function sleep(ms) {
|
|
115
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=resilient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilient.js","sourceRoot":"","sources":["../../src/providers/resilient.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { AuthResolver, ResolvedAuth } from "../auth/types.ts";
|
|
2
|
+
import type { Message, StopReason, ToolSchema, Usage } from "../core/types.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A model as the harness knows it: a stable alias plus the capability facts the
|
|
5
|
+
* agent loop needs in order to build a legal request.
|
|
6
|
+
*/
|
|
7
|
+
export interface ModelSpec {
|
|
8
|
+
/**
|
|
9
|
+
* Set when this entry only exists so a bare name resolves.
|
|
10
|
+
*
|
|
11
|
+
* Discovery registers `kimi-k3` alongside `moonshot/kimi-k3` so both work in
|
|
12
|
+
* `/model`. They are one model, so anything listing models for a human must
|
|
13
|
+
* skip the alias — otherwise every discovered model appears twice.
|
|
14
|
+
*/
|
|
15
|
+
aliasOf?: string;
|
|
16
|
+
/** Harness-level alias used by `/model` and config, e.g. "sonnet". */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Id of the provider that serves it, e.g. "anthropic". */
|
|
19
|
+
provider: string;
|
|
20
|
+
/** The wire model id sent to the API. */
|
|
21
|
+
model: string;
|
|
22
|
+
displayName?: string;
|
|
23
|
+
contextWindow: number;
|
|
24
|
+
maxOutputTokens: number;
|
|
25
|
+
supportsTools: boolean;
|
|
26
|
+
supportsImages: boolean;
|
|
27
|
+
supportsParallelToolCalls: boolean;
|
|
28
|
+
/** Prompt caching (Anthropic cache_control). */
|
|
29
|
+
supportsPromptCache: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Set when the capability flags above were assumed rather than published.
|
|
32
|
+
*
|
|
33
|
+
* A discovered endpoint that says nothing about its models lands on
|
|
34
|
+
* defaults, and "we were not told" is not the same fact as "no". It matters
|
|
35
|
+
* for images in particular: a false `supportsImages` makes the OpenAI
|
|
36
|
+
* adapter drop the images in a tool result on the floor, so a `read` of a
|
|
37
|
+
* screenshot would reach an entirely capable model as a caption. Anything
|
|
38
|
+
* refusing or degrading on a capability must check this first.
|
|
39
|
+
*/
|
|
40
|
+
capabilitiesAssumed?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* How this model wants reasoning configured. Getting this wrong is a 400,
|
|
43
|
+
* not a degradation:
|
|
44
|
+
* - "adaptive": `{type:"adaptive"}`; a `budget_tokens` config is rejected.
|
|
45
|
+
* - "always-on": thinking cannot be turned off; send no thinking config.
|
|
46
|
+
* - "budget": legacy `{type:"enabled", budget_tokens:N}`.
|
|
47
|
+
* - "effort": no thinking block, only an effort/verbosity tier.
|
|
48
|
+
* - "none": the model has no reasoning controls.
|
|
49
|
+
*/
|
|
50
|
+
thinkingMode: "adaptive" | "always-on" | "budget" | "effort" | "none";
|
|
51
|
+
/** Effort tiers the model accepts; empty means the parameter is unsupported. */
|
|
52
|
+
effortLevels: EffortLevel[];
|
|
53
|
+
/**
|
|
54
|
+
* Some models reject `thinking: {type:"disabled"}` above a given effort tier.
|
|
55
|
+
* Undefined means disabling is allowed at any tier this model supports.
|
|
56
|
+
*/
|
|
57
|
+
maxEffortWithThinkingDisabled?: EffortLevel;
|
|
58
|
+
/**
|
|
59
|
+
* Whether `temperature`/`top_p` are accepted. The current Claude and
|
|
60
|
+
* reasoning-model families reject them outright.
|
|
61
|
+
*/
|
|
62
|
+
supportsSampling: boolean;
|
|
63
|
+
costPer1MInput?: number;
|
|
64
|
+
costPer1MOutput?: number;
|
|
65
|
+
costPer1MCacheRead?: number;
|
|
66
|
+
costPer1MCacheWrite?: number;
|
|
67
|
+
}
|
|
68
|
+
export type EffortLevel = "low" | "medium" | "high" | "xhigh" | "max";
|
|
69
|
+
export interface ThinkingConfig {
|
|
70
|
+
enabled: boolean;
|
|
71
|
+
/** Only used by models whose `thinkingMode` is "budget" (or Gemini). */
|
|
72
|
+
budgetTokens?: number;
|
|
73
|
+
/** Effort tier; clamped to the model's `effortLevels`. */
|
|
74
|
+
effort?: EffortLevel;
|
|
75
|
+
/** Ask the provider to return a readable summary of the reasoning. */
|
|
76
|
+
display?: "summarized" | "omitted";
|
|
77
|
+
}
|
|
78
|
+
/** Clamp a requested effort tier to what a model actually accepts. */
|
|
79
|
+
export declare function resolveEffort(spec: ModelSpec, requested: EffortLevel | undefined): EffortLevel | undefined;
|
|
80
|
+
export type ToolChoice = {
|
|
81
|
+
type: "auto";
|
|
82
|
+
} | {
|
|
83
|
+
type: "none";
|
|
84
|
+
} | {
|
|
85
|
+
type: "required";
|
|
86
|
+
} | {
|
|
87
|
+
type: "tool";
|
|
88
|
+
name: string;
|
|
89
|
+
};
|
|
90
|
+
export interface GenerateRequest {
|
|
91
|
+
model: ModelSpec;
|
|
92
|
+
system?: string;
|
|
93
|
+
messages: Message[];
|
|
94
|
+
tools?: ToolSchema[];
|
|
95
|
+
toolChoice?: ToolChoice;
|
|
96
|
+
maxOutputTokens?: number;
|
|
97
|
+
temperature?: number;
|
|
98
|
+
topP?: number;
|
|
99
|
+
stopSequences?: string[];
|
|
100
|
+
thinking?: ThinkingConfig;
|
|
101
|
+
/** Enable provider prompt caching where supported. */
|
|
102
|
+
cache?: boolean;
|
|
103
|
+
signal?: AbortSignal;
|
|
104
|
+
/** Extra provider-specific body fields, merged last. */
|
|
105
|
+
extraBody?: Record<string, unknown>;
|
|
106
|
+
}
|
|
107
|
+
export type StreamEvent = {
|
|
108
|
+
type: "start";
|
|
109
|
+
model: string;
|
|
110
|
+
} | {
|
|
111
|
+
type: "text_delta";
|
|
112
|
+
text: string;
|
|
113
|
+
} | {
|
|
114
|
+
type: "thinking_delta";
|
|
115
|
+
text: string;
|
|
116
|
+
} | {
|
|
117
|
+
type: "thinking_signature";
|
|
118
|
+
signature: string;
|
|
119
|
+
} | {
|
|
120
|
+
type: "tool_call_start";
|
|
121
|
+
id: string;
|
|
122
|
+
name: string;
|
|
123
|
+
}
|
|
124
|
+
/** Raw JSON fragment of the tool input, for live previews. */
|
|
125
|
+
| {
|
|
126
|
+
type: "tool_call_delta";
|
|
127
|
+
id: string;
|
|
128
|
+
argsDelta: string;
|
|
129
|
+
} | {
|
|
130
|
+
type: "tool_call_end";
|
|
131
|
+
id: string;
|
|
132
|
+
name: string;
|
|
133
|
+
input: unknown;
|
|
134
|
+
} | {
|
|
135
|
+
type: "usage";
|
|
136
|
+
usage: Usage;
|
|
137
|
+
} | {
|
|
138
|
+
type: "end";
|
|
139
|
+
stopReason: StopReason;
|
|
140
|
+
usage: Usage;
|
|
141
|
+
};
|
|
142
|
+
export interface Provider {
|
|
143
|
+
readonly id: string;
|
|
144
|
+
/** Human-readable name for logs and `/model`. */
|
|
145
|
+
readonly name: string;
|
|
146
|
+
/**
|
|
147
|
+
* Cheap synchronous check for a statically-configured key. A provider
|
|
148
|
+
* authenticated through a subscription may return false here and still work
|
|
149
|
+
* — ask `AuthManager.isAuthenticated()` for the authoritative answer.
|
|
150
|
+
*/
|
|
151
|
+
isConfigured(): boolean;
|
|
152
|
+
stream(request: GenerateRequest): AsyncGenerator<StreamEvent, void, void>;
|
|
153
|
+
/**
|
|
154
|
+
* Report the models this account can actually use. Subscription providers
|
|
155
|
+
* gate models by entitlement, so a static catalog would over-promise.
|
|
156
|
+
*/
|
|
157
|
+
listModels?(): Promise<ModelSpec[]>;
|
|
158
|
+
}
|
|
159
|
+
export interface ProviderCredentials {
|
|
160
|
+
apiKey?: string;
|
|
161
|
+
baseUrl?: string;
|
|
162
|
+
/** Extra headers merged into every request. */
|
|
163
|
+
headers?: Record<string, string>;
|
|
164
|
+
/** Per-request timeout in ms. */
|
|
165
|
+
timeoutMs?: number;
|
|
166
|
+
/** Number of retries on retryable failures. */
|
|
167
|
+
maxRetries?: number;
|
|
168
|
+
/**
|
|
169
|
+
* Resolves credentials at request time. Required for subscription auth,
|
|
170
|
+
* where the token in hand at startup may have expired by the next turn.
|
|
171
|
+
*/
|
|
172
|
+
auth?: AuthResolver;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Merge resolved credentials into a request's headers.
|
|
176
|
+
*
|
|
177
|
+
* `removeHeaders` matters as much as the additions: a provider that sets
|
|
178
|
+
* `x-api-key` by default must have it stripped when a bearer token is in play,
|
|
179
|
+
* or the API sees two conflicting credentials.
|
|
180
|
+
*/
|
|
181
|
+
export declare function applyAuthHeaders(base: Record<string, string>, resolved: ResolvedAuth | undefined): Record<string, string>;
|
|
182
|
+
export declare function estimateCost(spec: ModelSpec, usage: Usage): number;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** Clamp a requested effort tier to what a model actually accepts. */
|
|
2
|
+
export function resolveEffort(spec, requested) {
|
|
3
|
+
if (spec.effortLevels.length === 0)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (requested && spec.effortLevels.includes(requested))
|
|
6
|
+
return requested;
|
|
7
|
+
if (!requested)
|
|
8
|
+
return undefined;
|
|
9
|
+
const order = ["low", "medium", "high", "xhigh", "max"];
|
|
10
|
+
const wanted = order.indexOf(requested);
|
|
11
|
+
// Step down to the highest supported tier at or below what was asked for.
|
|
12
|
+
for (let i = wanted; i >= 0; i--) {
|
|
13
|
+
const candidate = order[i];
|
|
14
|
+
if (spec.effortLevels.includes(candidate))
|
|
15
|
+
return candidate;
|
|
16
|
+
}
|
|
17
|
+
return spec.effortLevels[0];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Merge resolved credentials into a request's headers.
|
|
21
|
+
*
|
|
22
|
+
* `removeHeaders` matters as much as the additions: a provider that sets
|
|
23
|
+
* `x-api-key` by default must have it stripped when a bearer token is in play,
|
|
24
|
+
* or the API sees two conflicting credentials.
|
|
25
|
+
*/
|
|
26
|
+
export function applyAuthHeaders(base, resolved) {
|
|
27
|
+
if (!resolved)
|
|
28
|
+
return base;
|
|
29
|
+
const merged = { ...base };
|
|
30
|
+
for (const name of resolved.removeHeaders ?? []) {
|
|
31
|
+
delete merged[name];
|
|
32
|
+
delete merged[name.toLowerCase()];
|
|
33
|
+
}
|
|
34
|
+
for (const [name, value] of Object.entries(resolved.headers)) {
|
|
35
|
+
merged[name] = value;
|
|
36
|
+
}
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
39
|
+
export function estimateCost(spec, usage) {
|
|
40
|
+
const m = 1_000_000;
|
|
41
|
+
const inTok = usage.inputTokens ?? 0;
|
|
42
|
+
const outTok = usage.outputTokens ?? 0;
|
|
43
|
+
const cacheRead = usage.cacheReadTokens ?? 0;
|
|
44
|
+
const cacheWrite = usage.cacheWriteTokens ?? 0;
|
|
45
|
+
return ((inTok * (spec.costPer1MInput ?? 0)) / m +
|
|
46
|
+
(outTok * (spec.costPer1MOutput ?? 0)) / m +
|
|
47
|
+
(cacheRead * (spec.costPer1MCacheRead ?? spec.costPer1MInput ?? 0)) / m +
|
|
48
|
+
(cacheWrite * (spec.costPer1MCacheWrite ?? spec.costPer1MInput ?? 0)) / m);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAuFA,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAC3B,IAAe,EACf,SAAkC;IAElC,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACrD,IAAI,SAAS,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IACzE,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,KAAK,GAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACxC,0EAA0E;IAC1E,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC;AA4ED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAA4B,EAC5B,QAAkC;IAElC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,MAAM,GAA2B,EAAE,GAAG,IAAI,EAAE,CAAC;IACnD,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;QAChD,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7D,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACvB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAe,EAAE,KAAY;IACxD,MAAM,CAAC,GAAG,SAAS,CAAC;IACpB,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,IAAI,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,gBAAgB,IAAI,CAAC,CAAC;IAC/C,OAAO,CACL,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QACxC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1C,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,kBAAkB,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QACvE,CAAC,UAAU,GAAG,CAAC,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAC1E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A five-field cron parser.
|
|
3
|
+
*
|
|
4
|
+
* Written rather than pulled in because the harness has no runtime
|
|
5
|
+
* dependencies, and because the one rule everybody gets wrong — how
|
|
6
|
+
* day-of-month and day-of-week combine — is worth having under test rather
|
|
7
|
+
* than inherited.
|
|
8
|
+
*/
|
|
9
|
+
export interface CronFields {
|
|
10
|
+
minute: Set<number>;
|
|
11
|
+
hour: Set<number>;
|
|
12
|
+
dayOfMonth: Set<number>;
|
|
13
|
+
month: Set<number>;
|
|
14
|
+
dayOfWeek: Set<number>;
|
|
15
|
+
/**
|
|
16
|
+
* True when both day fields are restricted. Standard cron treats that as
|
|
17
|
+
* OR, not AND: `0 0 1 * 1` fires on the 1st *and* on every Monday. Reading
|
|
18
|
+
* it as AND is the classic way to build a job that almost never runs.
|
|
19
|
+
*/
|
|
20
|
+
dayFieldsAreOr: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function parseCron(expression: string): CronFields;
|
|
23
|
+
/** Whether a wall-clock instant matches, to the minute. */
|
|
24
|
+
export declare function matches(fields: CronFields, date: Date): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* The next instant at or after `from` that matches.
|
|
27
|
+
*
|
|
28
|
+
* Steps a minute at a time rather than solving the fields analytically: it is
|
|
29
|
+
* a few thousand iterations at worst, and it stays correct across DST shifts
|
|
30
|
+
* because it asks the platform's calendar rather than doing date arithmetic.
|
|
31
|
+
* Returns undefined if nothing matches within four years (e.g. Feb 30).
|
|
32
|
+
*/
|
|
33
|
+
export declare function nextRun(fields: CronFields, from?: Date): Date | undefined;
|
|
34
|
+
/** Describe a schedule in words, for `schedule list`. */
|
|
35
|
+
export declare function describeCron(expression: string): string;
|