@jmanuelcorral/openteam 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.
Files changed (66) hide show
  1. package/.opencode/openteam.example.json +57 -0
  2. package/AGENTS.md +41 -0
  3. package/LICENSE +21 -0
  4. package/README.md +315 -0
  5. package/dist/capabilities/budget.d.ts +12 -0
  6. package/dist/capabilities/budget.d.ts.map +1 -0
  7. package/dist/capabilities/catalog.d.ts +27 -0
  8. package/dist/capabilities/catalog.d.ts.map +1 -0
  9. package/dist/capabilities/classify.d.ts +10 -0
  10. package/dist/capabilities/classify.d.ts.map +1 -0
  11. package/dist/capabilities/curated.d.ts +3 -0
  12. package/dist/capabilities/curated.d.ts.map +1 -0
  13. package/dist/capabilities/normalize.d.ts +3 -0
  14. package/dist/capabilities/normalize.d.ts.map +1 -0
  15. package/dist/capabilities/select.d.ts +6 -0
  16. package/dist/capabilities/select.d.ts.map +1 -0
  17. package/dist/capabilities/types.d.ts +58 -0
  18. package/dist/capabilities/types.d.ts.map +1 -0
  19. package/dist/classifier/localClassifier.d.ts +19 -0
  20. package/dist/classifier/localClassifier.d.ts.map +1 -0
  21. package/dist/classifier/types.d.ts +20 -0
  22. package/dist/classifier/types.d.ts.map +1 -0
  23. package/dist/config/load.d.ts +4 -0
  24. package/dist/config/load.d.ts.map +1 -0
  25. package/dist/config/schema.d.ts +103 -0
  26. package/dist/config/schema.d.ts.map +1 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +1199 -0
  30. package/dist/local/foundry-local.d.ts +17 -0
  31. package/dist/local/foundry-local.d.ts.map +1 -0
  32. package/dist/local/lmstudio.d.ts +4 -0
  33. package/dist/local/lmstudio.d.ts.map +1 -0
  34. package/dist/local/ollama.d.ts +4 -0
  35. package/dist/local/ollama.d.ts.map +1 -0
  36. package/dist/local/openai-compatible.d.ts +25 -0
  37. package/dist/local/openai-compatible.d.ts.map +1 -0
  38. package/dist/local/registry.d.ts +20 -0
  39. package/dist/local/registry.d.ts.map +1 -0
  40. package/dist/local/types.d.ts +39 -0
  41. package/dist/local/types.d.ts.map +1 -0
  42. package/dist/orchestrator/coordinator.d.ts +43 -0
  43. package/dist/orchestrator/coordinator.d.ts.map +1 -0
  44. package/dist/orchestrator/permissions.d.ts +14 -0
  45. package/dist/orchestrator/permissions.d.ts.map +1 -0
  46. package/dist/orchestrator/roles.d.ts +40 -0
  47. package/dist/orchestrator/roles.d.ts.map +1 -0
  48. package/dist/orchestrator/subsessions.d.ts +62 -0
  49. package/dist/orchestrator/subsessions.d.ts.map +1 -0
  50. package/dist/plugin/availability.d.ts +20 -0
  51. package/dist/plugin/availability.d.ts.map +1 -0
  52. package/dist/plugin/hooks.d.ts +14 -0
  53. package/dist/plugin/hooks.d.ts.map +1 -0
  54. package/dist/router/chooseModel.d.ts +3 -0
  55. package/dist/router/chooseModel.d.ts.map +1 -0
  56. package/dist/router/types.d.ts +49 -0
  57. package/dist/router/types.d.ts.map +1 -0
  58. package/dist/telemetry/cost.d.ts +12 -0
  59. package/dist/telemetry/cost.d.ts.map +1 -0
  60. package/dist/telemetry/hash.d.ts +2 -0
  61. package/dist/telemetry/hash.d.ts.map +1 -0
  62. package/dist/telemetry/sink.d.ts +12 -0
  63. package/dist/telemetry/sink.d.ts.map +1 -0
  64. package/dist/telemetry/types.d.ts +30 -0
  65. package/dist/telemetry/types.d.ts.map +1 -0
  66. package/package.json +76 -0
package/dist/index.js ADDED
@@ -0,0 +1,1199 @@
1
+ // src/index.ts
2
+ import { appendFile, mkdir } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+
5
+ // src/config/schema.ts
6
+ import { z } from "zod";
7
+ var ModelRefSchema = z.object({
8
+ providerID: z.string().min(1),
9
+ modelID: z.string().min(1)
10
+ });
11
+ var RouterModeSchema = z.enum(["economy", "balanced", "quality"]);
12
+ var PrivacyModeSchema = z.enum([
13
+ "forceLocalOnSensitive",
14
+ "consentBeforeFrontier",
15
+ "off"
16
+ ]);
17
+ var BaselineModeSchema = z.enum(["auto", "pinned"]);
18
+ var defaultLocalModel = {
19
+ providerID: "ollama",
20
+ modelID: "qwen3:8b"
21
+ };
22
+ var defaultFrontierModel = {
23
+ providerID: "anthropic",
24
+ modelID: "claude-sonnet-4-5"
25
+ };
26
+ var LocalRuntimeSchema = z.object({
27
+ id: z.enum(["ollama", "lmstudio", "foundry-local"]),
28
+ enabled: z.boolean().default(true),
29
+ baseURL: z.string().url().optional(),
30
+ discovery: z.enum(["cli", "sdk", "manual"]).optional(),
31
+ defaultModel: ModelRefSchema
32
+ });
33
+ var OpenTeamConfigSchema = z.object({
34
+ baseline: z.object({
35
+ mode: BaselineModeSchema.default("auto"),
36
+ pinnedModel: ModelRefSchema.nullable().default(null),
37
+ hardDefault: ModelRefSchema.default(defaultFrontierModel)
38
+ }).default({
39
+ mode: "auto",
40
+ pinnedModel: null,
41
+ hardDefault: defaultFrontierModel
42
+ }),
43
+ router: z.object({
44
+ mode: RouterModeSchema.default("balanced"),
45
+ localDefault: ModelRefSchema.default(defaultLocalModel),
46
+ trivialPromptMaxChars: z.number().int().positive().default(280),
47
+ frontierPromptMinChars: z.number().int().positive().default(2000)
48
+ }).default({
49
+ mode: "balanced",
50
+ localDefault: defaultLocalModel,
51
+ trivialPromptMaxChars: 280,
52
+ frontierPromptMinChars: 2000
53
+ }),
54
+ local: z.object({
55
+ runtimes: z.array(LocalRuntimeSchema).min(1).default([
56
+ {
57
+ id: "ollama",
58
+ enabled: true,
59
+ baseURL: "http://localhost:11434/v1",
60
+ defaultModel: { providerID: "ollama", modelID: "qwen3:8b" }
61
+ }
62
+ ])
63
+ }).default({
64
+ runtimes: [
65
+ {
66
+ id: "ollama",
67
+ enabled: true,
68
+ baseURL: "http://localhost:11434/v1",
69
+ defaultModel: defaultLocalModel
70
+ }
71
+ ]
72
+ }),
73
+ budgets: z.object({
74
+ sessionUSD: z.number().positive().optional(),
75
+ monthlyUSD: z.number().positive().optional(),
76
+ frontierTokensPerSession: z.number().int().positive().optional(),
77
+ hardStopOnBudgetExhaustion: z.boolean().default(false)
78
+ }).default({ hardStopOnBudgetExhaustion: false }),
79
+ privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive")
80
+ });
81
+
82
+ // src/config/load.ts
83
+ function loadOpenTeamConfig(raw) {
84
+ return OpenTeamConfigSchema.parse(raw ?? {});
85
+ }
86
+
87
+ // src/local/openai-compatible.ts
88
+ function normalizeBaseURL(baseURL) {
89
+ return baseURL.replace(/\/+$/, "");
90
+ }
91
+ function modelsEndpoint(baseURL) {
92
+ return `${normalizeBaseURL(baseURL)}/models`;
93
+ }
94
+ function errorMessage(error) {
95
+ if (error instanceof Error) {
96
+ return error.message;
97
+ }
98
+ return String(error);
99
+ }
100
+ async function listOpenAICompatibleModels(baseURL, fetch) {
101
+ const response = await fetch(modelsEndpoint(baseURL), {
102
+ method: "GET",
103
+ headers: { accept: "application/json" }
104
+ });
105
+ if (!response.ok) {
106
+ throw new Error(`GET /models failed with HTTP ${response.status}`);
107
+ }
108
+ let payload;
109
+ try {
110
+ payload = await response.json();
111
+ } catch (error) {
112
+ throw new Error(`Malformed JSON from /models: ${errorMessage(error)}`);
113
+ }
114
+ return parseOpenAIModels(payload);
115
+ }
116
+ function parseOpenAIModels(payload) {
117
+ if (!isObject(payload) || !Array.isArray(payload.data)) {
118
+ throw new Error("Malformed /models response: expected data array");
119
+ }
120
+ return payload.data.map(parseOpenAIModel);
121
+ }
122
+ function unavailableSnapshot(input) {
123
+ return {
124
+ id: input.id,
125
+ baseURL: input.baseURL,
126
+ reachable: false,
127
+ models: [],
128
+ probedAt: input.probedAt,
129
+ error: errorMessage(input.error)
130
+ };
131
+ }
132
+ async function probeOpenAICompatibleRuntime(input) {
133
+ const normalizedBaseURL = normalizeBaseURL(input.baseURL);
134
+ try {
135
+ return {
136
+ id: input.id,
137
+ baseURL: normalizedBaseURL,
138
+ reachable: true,
139
+ models: await listOpenAICompatibleModels(normalizedBaseURL, input.fetch),
140
+ probedAt: input.probedAt
141
+ };
142
+ } catch (error) {
143
+ return unavailableSnapshot({
144
+ id: input.id,
145
+ baseURL: normalizedBaseURL,
146
+ probedAt: input.probedAt,
147
+ error
148
+ });
149
+ }
150
+ }
151
+ function parseOpenAIModel(value) {
152
+ if (!isObject(value) || typeof value.id !== "string" || value.id.length === 0) {
153
+ throw new Error("Malformed /models response: model id must be a string");
154
+ }
155
+ const contextWindow = readContextWindow(value);
156
+ const model = {
157
+ modelID: value.id,
158
+ supportsTools: readSupportsTools(value)
159
+ };
160
+ if (contextWindow !== undefined) {
161
+ model.contextWindow = contextWindow;
162
+ }
163
+ return model;
164
+ }
165
+ function readSupportsTools(model) {
166
+ const direct = readBoolean(model, [
167
+ "tool_call",
168
+ "tool_calls",
169
+ "toolCalling",
170
+ "supportsToolCalling",
171
+ "supports_tools",
172
+ "supportsTools"
173
+ ]);
174
+ if (direct !== undefined) {
175
+ return direct;
176
+ }
177
+ const capabilities = readCapabilityBoolean(model.capabilities);
178
+ if (capabilities !== undefined) {
179
+ return capabilities;
180
+ }
181
+ const features = readCapabilityBoolean(model.features);
182
+ if (features !== undefined) {
183
+ return features;
184
+ }
185
+ return "unknown";
186
+ }
187
+ function readCapabilityBoolean(value) {
188
+ if (Array.isArray(value)) {
189
+ if (value.some((entry) => typeof entry === "string" && ["tools", "tool_call", "tool-calling", "function_calling"].includes(entry))) {
190
+ return true;
191
+ }
192
+ return;
193
+ }
194
+ if (!isObject(value)) {
195
+ return;
196
+ }
197
+ return readBoolean(value, [
198
+ "tools",
199
+ "tool_call",
200
+ "tool_calls",
201
+ "toolCalling",
202
+ "function_calling",
203
+ "supportsToolCalling"
204
+ ]);
205
+ }
206
+ function readContextWindow(model) {
207
+ const direct = readPositiveInteger(model, [
208
+ "contextWindow",
209
+ "context_window",
210
+ "context_length",
211
+ "max_context_length",
212
+ "n_ctx"
213
+ ]);
214
+ if (direct !== undefined) {
215
+ return direct;
216
+ }
217
+ if (isObject(model.limit)) {
218
+ return readPositiveInteger(model.limit, ["context"]);
219
+ }
220
+ return;
221
+ }
222
+ function readBoolean(object, keys) {
223
+ for (const key of keys) {
224
+ const value = object[key];
225
+ if (typeof value === "boolean") {
226
+ return value;
227
+ }
228
+ }
229
+ return;
230
+ }
231
+ function readPositiveInteger(object, keys) {
232
+ for (const key of keys) {
233
+ const value = object[key];
234
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
235
+ return value;
236
+ }
237
+ }
238
+ return;
239
+ }
240
+ function isObject(value) {
241
+ return typeof value === "object" && value !== null && !Array.isArray(value);
242
+ }
243
+
244
+ // src/local/foundry-local.ts
245
+ async function discoverFoundryLocalBaseURL(input) {
246
+ const result = await input.exec(input.command ?? "foundry", [
247
+ ...input.args ?? ["service", "status"]
248
+ ]);
249
+ if (result.exitCode !== 0) {
250
+ throw new Error(`Foundry Local discovery failed with exit code ${result.exitCode}: ${result.stderr || result.stdout}`);
251
+ }
252
+ const endpoint = parseFoundryEndpoint(`${result.stdout}
253
+ ${result.stderr}`);
254
+ if (endpoint === undefined) {
255
+ throw new Error("Foundry Local discovery did not return an endpoint");
256
+ }
257
+ return endpoint;
258
+ }
259
+ function createFoundryLocalAdapter() {
260
+ return {
261
+ id: "foundry-local",
262
+ listModels: async (options) => {
263
+ const baseURL = await resolveFoundryBaseURL(options);
264
+ return listOpenAICompatibleModels(baseURL, options.fetch);
265
+ },
266
+ probe: async (options) => {
267
+ let baseURL = options.baseURL ?? "";
268
+ try {
269
+ baseURL = await resolveFoundryBaseURL(options);
270
+ } catch (error) {
271
+ return unavailableSnapshot({
272
+ id: "foundry-local",
273
+ baseURL,
274
+ probedAt: options.probedAt,
275
+ error
276
+ });
277
+ }
278
+ return probeOpenAICompatibleRuntime({
279
+ id: "foundry-local",
280
+ baseURL,
281
+ fetch: options.fetch,
282
+ probedAt: options.probedAt
283
+ });
284
+ }
285
+ };
286
+ }
287
+ async function resolveFoundryBaseURL(options) {
288
+ if (options.baseURL !== undefined) {
289
+ return normalizeBaseURL(options.baseURL);
290
+ }
291
+ if (options.discovery === undefined) {
292
+ throw new Error("Foundry Local discovery is required when baseURL is absent");
293
+ }
294
+ return normalizeFoundryDiscovery(await options.discovery());
295
+ }
296
+ function normalizeFoundryDiscovery(discovery) {
297
+ if (typeof discovery === "string") {
298
+ return ensureFoundryV1BaseURL(discovery);
299
+ }
300
+ if ("baseURL" in discovery) {
301
+ return ensureFoundryV1BaseURL(discovery.baseURL);
302
+ }
303
+ const host = discovery.host ?? "localhost";
304
+ const protocol = discovery.protocol ?? "http";
305
+ return `${protocol}://${host}:${discovery.port}/v1`;
306
+ }
307
+ function foundryDiscoveryFromExec(exec) {
308
+ return () => discoverFoundryLocalBaseURL({ exec });
309
+ }
310
+ function parseFoundryEndpoint(output) {
311
+ const explicitURL = output.match(/https?:\/\/[^\s"'<>),]+/u)?.[0];
312
+ if (explicitURL !== undefined) {
313
+ return ensureFoundryV1BaseURL(explicitURL);
314
+ }
315
+ const localhostPort = output.match(/(?:localhost|127\.0\.0\.1|\[::1\])\s*:\s*(\d{2,5})/u);
316
+ const port = localhostPort?.[1];
317
+ if (port !== undefined) {
318
+ return `http://localhost:${port}/v1`;
319
+ }
320
+ return;
321
+ }
322
+ function ensureFoundryV1BaseURL(endpoint) {
323
+ const normalizedEndpoint = endpoint.trim().replace(/[),.;]+$/u, "");
324
+ const withProtocol = /^[a-z]+:\/\//iu.test(normalizedEndpoint) ? normalizedEndpoint : `http://${normalizedEndpoint}`;
325
+ try {
326
+ const url = new URL(withProtocol);
327
+ const path = normalizeBaseURL(url.pathname);
328
+ if (path.endsWith("/v1")) {
329
+ return normalizeBaseURL(url.toString());
330
+ }
331
+ return `${url.origin}/v1`;
332
+ } catch (error) {
333
+ throw new Error(`Invalid Foundry Local endpoint: ${errorMessage(error)}`);
334
+ }
335
+ }
336
+
337
+ // src/local/lmstudio.ts
338
+ var LMSTUDIO_DEFAULT_BASE_URL = "http://localhost:1234/v1";
339
+ function createLMStudioAdapter() {
340
+ return {
341
+ id: "lmstudio",
342
+ defaultBaseURL: LMSTUDIO_DEFAULT_BASE_URL,
343
+ listModels(options) {
344
+ return listOpenAICompatibleModels(normalizeBaseURL(options.baseURL ?? LMSTUDIO_DEFAULT_BASE_URL), options.fetch);
345
+ },
346
+ probe(options) {
347
+ return probeOpenAICompatibleRuntime({
348
+ id: "lmstudio",
349
+ baseURL: options.baseURL ?? LMSTUDIO_DEFAULT_BASE_URL,
350
+ fetch: options.fetch,
351
+ probedAt: options.probedAt
352
+ });
353
+ }
354
+ };
355
+ }
356
+
357
+ // src/local/ollama.ts
358
+ var OLLAMA_DEFAULT_BASE_URL = "http://localhost:11434/v1";
359
+ function createOllamaAdapter() {
360
+ return {
361
+ id: "ollama",
362
+ defaultBaseURL: OLLAMA_DEFAULT_BASE_URL,
363
+ listModels(options) {
364
+ return listOpenAICompatibleModels(normalizeBaseURL(options.baseURL ?? OLLAMA_DEFAULT_BASE_URL), options.fetch);
365
+ },
366
+ probe(options) {
367
+ return probeOpenAICompatibleRuntime({
368
+ id: "ollama",
369
+ baseURL: options.baseURL ?? OLLAMA_DEFAULT_BASE_URL,
370
+ fetch: options.fetch,
371
+ probedAt: options.probedAt
372
+ });
373
+ }
374
+ };
375
+ }
376
+
377
+ // src/local/registry.ts
378
+ class RuntimeRegistry {
379
+ adapters;
380
+ clock;
381
+ fetch;
382
+ foundryDiscovery;
383
+ constructor(options) {
384
+ this.adapters = {
385
+ ollama: createOllamaAdapter(),
386
+ lmstudio: createLMStudioAdapter(),
387
+ "foundry-local": createFoundryLocalAdapter(),
388
+ ...options.adapters
389
+ };
390
+ this.clock = options.clock;
391
+ this.fetch = options.fetch;
392
+ this.foundryDiscovery = options.foundryDiscovery;
393
+ }
394
+ async probe(runtimes) {
395
+ const snapshots = [];
396
+ for (const runtime of runtimes) {
397
+ if (!runtime.enabled) {
398
+ continue;
399
+ }
400
+ const probedAt = this.clock();
401
+ const adapter = this.adapters[runtime.id];
402
+ const options = this.createProbeOptions(runtime, probedAt);
403
+ try {
404
+ snapshots.push(await adapter.probe(options));
405
+ } catch (error) {
406
+ snapshots.push(unavailableSnapshot({
407
+ id: runtime.id,
408
+ baseURL: runtime.baseURL ?? adapter.defaultBaseURL ?? "",
409
+ probedAt,
410
+ error: `Adapter threw unexpectedly: ${errorMessage(error)}`
411
+ }));
412
+ }
413
+ }
414
+ return snapshots;
415
+ }
416
+ createProbeOptions(runtime, probedAt) {
417
+ const options = {
418
+ fetch: this.fetch,
419
+ probedAt
420
+ };
421
+ if (runtime.baseURL !== undefined) {
422
+ options.baseURL = runtime.baseURL;
423
+ }
424
+ if (runtime.id === "foundry-local" && runtime.discovery !== "manual" && this.foundryDiscovery !== undefined) {
425
+ options.discovery = this.foundryDiscovery;
426
+ }
427
+ return options;
428
+ }
429
+ }
430
+
431
+ // src/plugin/availability.ts
432
+ function modelKey(model) {
433
+ return `${model.providerID}/${model.modelID}`;
434
+ }
435
+ function compareAvailableModels(left, right) {
436
+ const kindOrder = Number(left.kind === "frontier") - Number(right.kind === "frontier");
437
+ if (kindOrder !== 0) {
438
+ return kindOrder;
439
+ }
440
+ return modelKey(left).localeCompare(modelKey(right));
441
+ }
442
+ function uniqueSelections(models) {
443
+ const byKey = new Map;
444
+ for (const model of models) {
445
+ byKey.set(modelKey(model), model);
446
+ }
447
+ return [...byKey.values()].sort((left, right) => modelKey(left).localeCompare(modelKey(right)));
448
+ }
449
+ function frontierBaselines(config) {
450
+ const baselines = [];
451
+ if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
452
+ baselines.push(config.baseline.pinnedModel);
453
+ }
454
+ baselines.push(config.baseline.hardDefault);
455
+ return uniqueSelections(baselines);
456
+ }
457
+ function defaultLocalModels(config) {
458
+ const enabledDefaults = config.local.runtimes.filter((runtime) => runtime.enabled).map((runtime) => runtime.defaultModel);
459
+ return uniqueSelections(enabledDefaults.length > 0 ? enabledDefaults : [config.router.localDefault]);
460
+ }
461
+ function cloneAvailableModels(models) {
462
+ return models.map((model) => ({ ...model }));
463
+ }
464
+ function defaultAvailableModels(config) {
465
+ return [
466
+ ...defaultLocalModels(config).map((model) => ({
467
+ ...model,
468
+ kind: "local",
469
+ available: true
470
+ })),
471
+ ...frontierBaselines(config).map((model) => ({
472
+ ...model,
473
+ kind: "frontier",
474
+ available: true
475
+ }))
476
+ ].sort(compareAvailableModels);
477
+ }
478
+ function toAvailableModels(snapshots, config) {
479
+ const localModels = snapshots.flatMap((snapshot) => snapshot.models.map((model) => {
480
+ const availableModel = {
481
+ providerID: snapshot.id,
482
+ modelID: model.modelID,
483
+ kind: "local",
484
+ available: snapshot.reachable
485
+ };
486
+ if (model.supportsTools !== "unknown") {
487
+ availableModel.supportsTools = model.supportsTools;
488
+ }
489
+ if (model.contextWindow !== undefined) {
490
+ availableModel.contextWindow = model.contextWindow;
491
+ }
492
+ return availableModel;
493
+ }));
494
+ const frontierModels = frontierBaselines(config).map((model) => ({
495
+ ...model,
496
+ kind: "frontier",
497
+ available: true
498
+ }));
499
+ return [...localModels, ...frontierModels].sort(compareAvailableModels);
500
+ }
501
+ function createAvailabilityCache(options) {
502
+ let cached = defaultAvailableModels(options.config);
503
+ let inFlight;
504
+ async function refreshNow() {
505
+ try {
506
+ const snapshots = await options.registry.probe(options.config.local.runtimes);
507
+ cached = toAvailableModels(snapshots, options.config);
508
+ } catch (error) {
509
+ options.onRefreshError?.(error);
510
+ }
511
+ }
512
+ return {
513
+ get: () => cloneAvailableModels(cached),
514
+ refresh: () => {
515
+ inFlight ??= refreshNow().finally(() => {
516
+ inFlight = undefined;
517
+ });
518
+ return inFlight;
519
+ }
520
+ };
521
+ }
522
+
523
+ // src/capabilities/budget.ts
524
+ var WARNING_UTILIZATION = 0.8;
525
+ function projectedSpend(current, estimatedCostUSD) {
526
+ return Math.max(0, current) + Math.max(0, estimatedCostUSD);
527
+ }
528
+ function budgetExceeded(projected, limit) {
529
+ return limit !== undefined && projected >= limit;
530
+ }
531
+ function budgetNearLimit(projected, limit) {
532
+ return limit !== undefined && projected >= limit * WARNING_UTILIZATION && projected < limit;
533
+ }
534
+ function exhaustedDecision(budgets, reason) {
535
+ return {
536
+ action: budgets.hardStopOnBudgetExhaustion ? "blockFrontier" : "forceLocal",
537
+ reason
538
+ };
539
+ }
540
+ function evaluateBudget(state, estimatedCostUSD, budgets) {
541
+ const projectedSessionUSD = projectedSpend(state.sessionUSD, estimatedCostUSD);
542
+ const projectedMonthlyUSD = budgets.monthlyUSD === undefined ? undefined : projectedSpend(state.monthlyUSD ?? 0, estimatedCostUSD);
543
+ if (budgetExceeded(projectedSessionUSD, budgets.sessionUSD)) {
544
+ return exhaustedDecision(budgets, "session-budget-exhausted");
545
+ }
546
+ if (budgetExceeded(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
547
+ return exhaustedDecision(budgets, "monthly-budget-exhausted");
548
+ }
549
+ if (state.frontierTokens !== undefined && budgets.frontierTokensPerSession !== undefined && state.frontierTokens >= budgets.frontierTokensPerSession) {
550
+ return exhaustedDecision(budgets, "frontier-token-budget-exhausted");
551
+ }
552
+ if (budgetNearLimit(projectedSessionUSD, budgets.sessionUSD)) {
553
+ return { action: "warn", reason: "session-budget-near-limit" };
554
+ }
555
+ if (budgetNearLimit(projectedMonthlyUSD ?? 0, budgets.monthlyUSD)) {
556
+ return { action: "warn", reason: "monthly-budget-near-limit" };
557
+ }
558
+ return { action: "ok" };
559
+ }
560
+
561
+ // src/capabilities/classify.ts
562
+ var hardKeywords = [
563
+ "security",
564
+ "auth",
565
+ "authentication",
566
+ "authorization",
567
+ "concurrency",
568
+ "race condition",
569
+ "architecture",
570
+ "refactor",
571
+ "migration",
572
+ "debug",
573
+ "root cause"
574
+ ];
575
+ function scoreOverride(override) {
576
+ if (override === "alwaysFrontier") {
577
+ return 30;
578
+ }
579
+ if (override === "alwaysLocal") {
580
+ return -20;
581
+ }
582
+ return 0;
583
+ }
584
+ function tierFromScore(score) {
585
+ if (score <= 15) {
586
+ return "trivial";
587
+ }
588
+ if (score <= 35) {
589
+ return "simple";
590
+ }
591
+ if (score <= 65) {
592
+ return "moderate";
593
+ }
594
+ return "hard";
595
+ }
596
+ function confidenceFromScore(score) {
597
+ if (score >= 75 || score <= 10) {
598
+ return 0.9;
599
+ }
600
+ if (score >= 30 && score <= 70) {
601
+ return 0.58;
602
+ }
603
+ return 0.72;
604
+ }
605
+ function addScore(condition, amount, reason, state) {
606
+ if (condition) {
607
+ state.score += amount;
608
+ state.reasons.push(reason);
609
+ }
610
+ }
611
+ function classifyHeuristic(task) {
612
+ const state = { score: scoreOverride(task.explicitOverride), reasons: [] };
613
+ const prompt = task.prompt?.toLowerCase() ?? "";
614
+ const estimatedTokens = (task.estimatedInputTokens ?? 0) + (task.estimatedOutputTokens ?? 0);
615
+ addScore(task.promptChars <= 80 && !task.hasCode && task.requiresTools !== true, -5, "short-text-prompt", state);
616
+ addScore(task.promptChars > 1500 || estimatedTokens > 700, 10, "large-prompt", state);
617
+ addScore(task.promptChars > 5000 || estimatedTokens > 3000, 15, "very-large-context", state);
618
+ addScore(task.hasCode, 15, "code-present", state);
619
+ addScore(task.requiresTools === true, 15, "tools-required", state);
620
+ addScore(task.requiresVision === true, 15, "vision-required", state);
621
+ addScore((task.fileCount ?? 0) > 1, 20, "multi-file-task", state);
622
+ addScore((task.diffHunks ?? 0) > 2, 15, "multi-hunk-diff", state);
623
+ addScore(hardKeywords.some((keyword) => prompt.includes(keyword)), 25, "hard-keyword", state);
624
+ addScore(task.userAskedForQuality === true, 20, "quality-requested", state);
625
+ addScore(task.privacySensitive === true, 10, "privacy-sensitive", state);
626
+ const normalizedScore = Math.max(0, Math.min(100, state.score));
627
+ return {
628
+ tier: tierFromScore(normalizedScore),
629
+ confidence: confidenceFromScore(normalizedScore),
630
+ reasons: state.reasons.length > 0 ? state.reasons : ["no-hard-signals"],
631
+ ambiguous: normalizedScore >= 30 && normalizedScore <= 70
632
+ };
633
+ }
634
+
635
+ // src/capabilities/select.ts
636
+ var BLENDED_COST_INPUT_WEIGHT = 0.75;
637
+ var BLENDED_COST_OUTPUT_WEIGHT = 1 - BLENDED_COST_INPUT_WEIGHT;
638
+ var availabilityRank = {
639
+ available: 0,
640
+ degraded: 1,
641
+ unavailable: 2
642
+ };
643
+ var tierRequirements = {
644
+ trivial: { reasoning: 0, code: 1, context: 8000 },
645
+ simple: { reasoning: 1, code: 2, context: 16000 },
646
+ moderate: { reasoning: 3, code: 3, context: 32000 },
647
+ hard: { reasoning: 4, code: 5, context: 64000 }
648
+ };
649
+ function deriveRequirement(tier, task) {
650
+ const base = tierRequirements[tier];
651
+ const estimatedContext = (task.estimatedInputTokens ?? 0) + (task.estimatedOutputTokens ?? 0);
652
+ const contextFromPrompt = Math.ceil(task.promptChars / 4);
653
+ const minContextWindow = Math.max(base.context, estimatedContext, contextFromPrompt);
654
+ const codeBump = task.hasCode ? 1 : 0;
655
+ return {
656
+ minReasoningTier: base.reasoning,
657
+ minCodeQualityTier: Math.min(5, base.code + codeBump),
658
+ minContextWindow,
659
+ needsTools: task.requiresTools === true,
660
+ needsVision: task.requiresVision === true
661
+ };
662
+ }
663
+ function blendedCostPer1M(profile) {
664
+ return profile.costPer1M.inputUSD * BLENDED_COST_INPUT_WEIGHT + profile.costPer1M.outputUSD * BLENDED_COST_OUTPUT_WEIGHT;
665
+ }
666
+ function modelKey2(profile) {
667
+ return `${profile.ref.providerID}/${profile.ref.modelID}`;
668
+ }
669
+ function compareCapableFrontier(req, left, right) {
670
+ const costDelta = blendedCostPer1M(left) - blendedCostPer1M(right);
671
+ if (costDelta !== 0) {
672
+ return costDelta;
673
+ }
674
+ const availabilityDelta = availabilityRank[left.availability] - availabilityRank[right.availability];
675
+ if (availabilityDelta !== 0) {
676
+ return availabilityDelta;
677
+ }
678
+ const reasoningWasteDelta = left.reasoningTier - req.minReasoningTier - (right.reasoningTier - req.minReasoningTier);
679
+ if (reasoningWasteDelta !== 0) {
680
+ return reasoningWasteDelta;
681
+ }
682
+ return modelKey2(left).localeCompare(modelKey2(right));
683
+ }
684
+ function satisfiesRequirement(req, profile) {
685
+ return profile.kind === "frontier" && profile.availability !== "unavailable" && profile.contextWindow >= req.minContextWindow && profile.reasoningTier >= req.minReasoningTier && profile.codeQualityTier >= req.minCodeQualityTier && (!req.needsTools || profile.supportsToolCalling) && (!req.needsVision || profile.supportsVision);
686
+ }
687
+ function selectCheapestCapableFrontier(req, profiles) {
688
+ const selected = profiles.filter((profile) => satisfiesRequirement(req, profile)).sort((left, right) => compareCapableFrontier(req, left, right))[0];
689
+ return selected?.ref ?? null;
690
+ }
691
+
692
+ // src/router/chooseModel.ts
693
+ function resolveConfiguredFrontierBaseline(config) {
694
+ if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
695
+ return config.baseline.pinnedModel;
696
+ }
697
+ return config.baseline.hardDefault;
698
+ }
699
+ function resolveFrontierBaseline(input, tier) {
700
+ if (input.config.baseline.mode === "pinned" && input.config.baseline.pinnedModel !== null) {
701
+ return { model: input.config.baseline.pinnedModel, rationale: [] };
702
+ }
703
+ if (input.profiles !== undefined) {
704
+ const selected = selectCheapestCapableFrontier(deriveRequirement(tier, input.task), input.profiles);
705
+ if (selected !== null) {
706
+ return {
707
+ model: selected,
708
+ rationale: ["auto-cheapest-capable-frontier"]
709
+ };
710
+ }
711
+ return {
712
+ model: input.config.baseline.hardDefault,
713
+ rationale: ["auto-cheapest-capable-unavailable-hard-default"]
714
+ };
715
+ }
716
+ return { model: input.config.baseline.hardDefault, rationale: [] };
717
+ }
718
+ function localDefault(config) {
719
+ return config.local.runtimes.find((runtime) => runtime.enabled)?.defaultModel ?? config.router.localDefault;
720
+ }
721
+ function modelKey3(model) {
722
+ return `${model.providerID}/${model.modelID}`;
723
+ }
724
+ function sameModel(left, right) {
725
+ return left.providerID === right.providerID && left.modelID === right.modelID;
726
+ }
727
+ function compareModels(left, right) {
728
+ return modelKey3(left).localeCompare(modelKey3(right));
729
+ }
730
+ function uniqueModelSelections(models) {
731
+ const byKey = new Map;
732
+ for (const model of models) {
733
+ byKey.set(modelKey3(model), model);
734
+ }
735
+ return [...byKey.values()].sort(compareModels);
736
+ }
737
+ function defaultAvailableModels2(config) {
738
+ return [
739
+ {
740
+ ...localDefault(config),
741
+ kind: "local",
742
+ available: true
743
+ },
744
+ {
745
+ ...resolveConfiguredFrontierBaseline(config),
746
+ kind: "frontier",
747
+ available: true
748
+ }
749
+ ].sort(compareAvailableModels2);
750
+ }
751
+ function compareAvailableModels2(left, right) {
752
+ return compareModels(left, right);
753
+ }
754
+ function availableModels(input) {
755
+ return (input.availableModels ?? defaultAvailableModels2(input.config)).slice().sort(compareAvailableModels2);
756
+ }
757
+ function listedModel(input, model) {
758
+ return availableModels(input).find((candidate) => sameModel(candidate, model));
759
+ }
760
+ function localStatus(input, model) {
761
+ const listed = listedModel(input, model);
762
+ return {
763
+ available: input.availableModels === undefined ? listed?.available !== false : listed?.available === true,
764
+ missingTools: input.task.requiresTools === true && listed?.supportsTools === false
765
+ };
766
+ }
767
+ function frontierAvailable(input, model) {
768
+ return listedModel(input, model)?.available !== false;
769
+ }
770
+ function localCandidateIsUsable(input, model) {
771
+ return model.kind === "local" && model.available && !(input.task.requiresTools === true && model.supportsTools === false);
772
+ }
773
+ function availableLocalFallbacks(input, selected) {
774
+ return uniqueModelSelections(availableModels(input).filter((model) => localCandidateIsUsable(input, model)).filter((model) => !sameModel(model, selected)).map((model) => ({
775
+ providerID: model.providerID,
776
+ modelID: model.modelID
777
+ })));
778
+ }
779
+ function buildFallbackChain(input, selected, frontier, allowFrontier) {
780
+ const chain = availableLocalFallbacks(input, selected);
781
+ if (allowFrontier && !sameModel(frontier, selected) && frontierAvailable(input, frontier)) {
782
+ chain.push(frontier);
783
+ }
784
+ return chain;
785
+ }
786
+ function inferRouteKind(config, selected, listed) {
787
+ if (listed !== undefined) {
788
+ return listed.kind;
789
+ }
790
+ return config.local.runtimes.some((runtime) => sameModel(runtime.defaultModel, selected)) ? "local" : "frontier";
791
+ }
792
+ function localFallbackReason(status) {
793
+ return status.missingTools ? "local-missing-tools-fallback" : "local-unavailable-fallback";
794
+ }
795
+ function decision(selected, routeKind, rationale, fallbackChain) {
796
+ return {
797
+ selected,
798
+ routeKind,
799
+ rationale,
800
+ fallbackChain
801
+ };
802
+ }
803
+ function resolveLocalPrimary(input, primary, frontier, options) {
804
+ const status = localStatus(input, primary);
805
+ if (status.available && !status.missingTools) {
806
+ return decision(primary, "local", options.baseRationale, buildFallbackChain(input, primary, frontier, options.allowFrontier));
807
+ }
808
+ const fallbackReason = localFallbackReason(status);
809
+ const nextLocal = availableLocalFallbacks(input, primary)[0];
810
+ if (nextLocal !== undefined) {
811
+ return decision(nextLocal, "local", [...options.baseRationale, fallbackReason], buildFallbackChain(input, nextLocal, frontier, options.allowFrontier));
812
+ }
813
+ if (!options.allowFrontier) {
814
+ return decision(primary, "local", [...options.baseRationale, fallbackReason, options.noLocalRationale], []);
815
+ }
816
+ if (frontierAvailable(input, frontier)) {
817
+ return decision(frontier, "frontier", [...options.baseRationale, fallbackReason, "no-local-available-frontier"], []);
818
+ }
819
+ return decision(primary, "local", [...options.baseRationale, fallbackReason, "frontier-unavailable"], []);
820
+ }
821
+ function resolveFrontierPrimary(input, frontier, rationale) {
822
+ if (frontierAvailable(input, frontier)) {
823
+ return decision(frontier, "frontier", rationale, []);
824
+ }
825
+ const nextLocal = availableLocalFallbacks(input, frontier)[0];
826
+ if (nextLocal !== undefined) {
827
+ return decision(nextLocal, "local", [...rationale, "frontier-unavailable-fallback"], buildFallbackChain(input, nextLocal, frontier, false));
828
+ }
829
+ return decision(frontier, "frontier", [...rationale, "frontier-unavailable"], []);
830
+ }
831
+ function tierForInput(input) {
832
+ return input.tier ?? classifyHeuristic(input.task).tier;
833
+ }
834
+ function modelProfile(input, model) {
835
+ return input.profiles?.find((profile) => sameModel(profile.ref, model));
836
+ }
837
+ function estimatedCostUSD(input, model) {
838
+ const profile = modelProfile(input, model);
839
+ if (profile === undefined) {
840
+ return 0;
841
+ }
842
+ const inputTokens = input.task.estimatedInputTokens ?? 0;
843
+ const outputTokens = input.task.estimatedOutputTokens ?? 0;
844
+ return inputTokens / 1e6 * profile.costPer1M.inputUSD + outputTokens / 1e6 * profile.costPer1M.outputUSD;
845
+ }
846
+ function roundUSD(value) {
847
+ return Math.round(value * 1000000000000) / 1000000000000;
848
+ }
849
+ function finalizeDecision(input, tier, frontier, budgetAction, core) {
850
+ const selectedCostUSD = estimatedCostUSD(input, core.selected);
851
+ const baselineCostUSD = estimatedCostUSD(input, frontier.model);
852
+ const savingsUSD = core.routeKind === "frontier" && sameModel(core.selected, frontier.model) ? 0 : baselineCostUSD - selectedCostUSD;
853
+ return {
854
+ ...core,
855
+ tier,
856
+ rationale: [...frontier.rationale, ...core.rationale],
857
+ estimatedCostUSD: roundUSD(selectedCostUSD),
858
+ baselineCostUSD: roundUSD(baselineCostUSD),
859
+ estimatedSavingsUSD: roundUSD(savingsUSD),
860
+ budgetAction
861
+ };
862
+ }
863
+ function chooseModel(input) {
864
+ const tier = tierForInput(input);
865
+ const local = localDefault(input.config);
866
+ const frontier = resolveFrontierBaseline(input, tier);
867
+ const frontierCostUSD = estimatedCostUSD(input, frontier.model);
868
+ const budgetAction = input.budgetState === undefined ? { action: "ok" } : evaluateBudget(input.budgetState, frontierCostUSD, input.config.budgets);
869
+ const override = input.task.explicitOverride;
870
+ if (input.task.privacySensitive === true && input.config.privacyMode === "forceLocalOnSensitive") {
871
+ return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
872
+ baseRationale: ["privacy-force-local"],
873
+ allowFrontier: false,
874
+ noLocalRationale: "privacy-force-local-unavailable"
875
+ }));
876
+ }
877
+ if (budgetAction.action === "forceLocal" || budgetAction.action === "blockFrontier") {
878
+ return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
879
+ baseRationale: [`budget-${budgetAction.action}`],
880
+ allowFrontier: false,
881
+ noLocalRationale: "budget-local-unavailable"
882
+ }));
883
+ }
884
+ if (typeof override === "object") {
885
+ return finalizeDecision(input, tier, frontier, budgetAction, decision(override, inferRouteKind(input.config, override, listedModel(input, override)), ["explicit-model-override"], buildFallbackChain(input, override, frontier.model, true)));
886
+ }
887
+ if (override === "alwaysLocal") {
888
+ return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
889
+ baseRationale: ["explicit-always-local"],
890
+ allowFrontier: false,
891
+ noLocalRationale: "forced-local-unavailable"
892
+ }));
893
+ }
894
+ if (override === "alwaysFrontier") {
895
+ return finalizeDecision(input, tier, frontier, budgetAction, decision(frontier.model, "frontier", ["explicit-always-frontier"], []));
896
+ }
897
+ const shortEnoughForLocal = input.task.promptChars <= input.config.router.trivialPromptMaxChars;
898
+ const largeEnoughForFrontier = input.task.promptChars >= input.config.router.frontierPromptMinChars;
899
+ if (!input.task.hasCode && shortEnoughForLocal && !largeEnoughForFrontier) {
900
+ return finalizeDecision(input, tier, frontier, budgetAction, resolveLocalPrimary(input, local, frontier.model, {
901
+ baseRationale: ["trivial-short-no-code"],
902
+ allowFrontier: true,
903
+ noLocalRationale: "forced-local-unavailable"
904
+ }));
905
+ }
906
+ return finalizeDecision(input, tier, frontier, budgetAction, resolveFrontierPrimary(input, frontier.model, [
907
+ input.task.hasCode ? "code-detected" : "large-or-nontrivial-prompt"
908
+ ]));
909
+ }
910
+
911
+ // src/telemetry/cost.ts
912
+ function routeKind(decision2) {
913
+ return decision2.routeKind === "local" ? "local" : "frontier";
914
+ }
915
+ function budgetAction(decision2) {
916
+ const reason = decision2.budgetAction.reason;
917
+ return reason === undefined ? decision2.budgetAction.action : `${decision2.budgetAction.action}:${reason}`;
918
+ }
919
+ function toCostRecord(decision2, context) {
920
+ const record = {
921
+ ts: context.ts,
922
+ promptHash: context.promptHash,
923
+ promptChars: context.promptChars,
924
+ tier: decision2.tier,
925
+ routeKind: routeKind(decision2),
926
+ selected: decision2.selected,
927
+ rationale: decision2.rationale.join(","),
928
+ estimatedCostUSD: decision2.estimatedCostUSD,
929
+ baselineCostUSD: decision2.baselineCostUSD,
930
+ estimatedSavingsUSD: decision2.estimatedSavingsUSD,
931
+ budgetAction: budgetAction(decision2)
932
+ };
933
+ if (context.sessionID !== undefined) {
934
+ record.sessionID = context.sessionID;
935
+ }
936
+ if (context.tokensIn !== undefined) {
937
+ record.tokensIn = context.tokensIn;
938
+ }
939
+ if (context.tokensOut !== undefined) {
940
+ record.tokensOut = context.tokensOut;
941
+ }
942
+ return record;
943
+ }
944
+
945
+ // src/telemetry/hash.ts
946
+ var FNV_OFFSET_BASIS = 2166136261;
947
+ var FNV_PRIME = 16777619;
948
+ function hashPrompt(prompt) {
949
+ let hash = FNV_OFFSET_BASIS;
950
+ for (let index = 0;index < prompt.length; index += 1) {
951
+ hash ^= prompt.charCodeAt(index);
952
+ hash = Math.imul(hash, FNV_PRIME);
953
+ }
954
+ return `fnv1a32:${(hash >>> 0).toString(16).padStart(8, "0")}`;
955
+ }
956
+
957
+ // src/telemetry/types.ts
958
+ import { z as z3 } from "zod";
959
+
960
+ // src/capabilities/types.ts
961
+ import { z as z2 } from "zod";
962
+ var CapabilityTierSchema = z2.union([
963
+ z2.literal(0),
964
+ z2.literal(1),
965
+ z2.literal(2),
966
+ z2.literal(3),
967
+ z2.literal(4),
968
+ z2.literal(5)
969
+ ]);
970
+ var ComplexityTierSchema = z2.enum([
971
+ "trivial",
972
+ "simple",
973
+ "moderate",
974
+ "hard"
975
+ ]);
976
+ var ModelCapabilityProfileSchema = z2.object({
977
+ ref: z2.object({
978
+ providerID: z2.string().min(1),
979
+ modelID: z2.string().min(1)
980
+ }),
981
+ kind: z2.enum(["local", "frontier", "router"]),
982
+ contextWindow: z2.number().int().positive(),
983
+ maxOutputTokens: z2.number().int().positive(),
984
+ supportsToolCalling: z2.boolean(),
985
+ supportsVision: z2.boolean(),
986
+ reasoningTier: CapabilityTierSchema,
987
+ codeQualityTier: CapabilityTierSchema,
988
+ costPer1M: z2.object({
989
+ inputUSD: z2.number().min(0),
990
+ outputUSD: z2.number().min(0)
991
+ }),
992
+ availability: z2.enum(["available", "degraded", "unavailable"])
993
+ });
994
+
995
+ // src/telemetry/types.ts
996
+ var CostRecordSchema = z3.object({
997
+ ts: z3.number().finite(),
998
+ sessionID: z3.string().min(1).optional(),
999
+ promptHash: z3.string().min(1),
1000
+ promptChars: z3.number().int().min(0),
1001
+ tier: ComplexityTierSchema,
1002
+ routeKind: z3.enum(["local", "frontier"]),
1003
+ selected: ModelRefSchema,
1004
+ rationale: z3.string(),
1005
+ estimatedCostUSD: z3.number().finite().min(0),
1006
+ baselineCostUSD: z3.number().finite().min(0),
1007
+ estimatedSavingsUSD: z3.number().finite(),
1008
+ budgetAction: z3.string().min(1),
1009
+ tokensIn: z3.number().int().min(0).optional(),
1010
+ tokensOut: z3.number().int().min(0).optional()
1011
+ });
1012
+
1013
+ // src/telemetry/sink.ts
1014
+ function createJsonlSink(deps) {
1015
+ return {
1016
+ record: async (record) => {
1017
+ try {
1018
+ const parsed = CostRecordSchema.parse(record);
1019
+ await deps.append(`${JSON.stringify(parsed)}
1020
+ `);
1021
+ } catch (error) {
1022
+ deps.onError?.(error);
1023
+ }
1024
+ }
1025
+ };
1026
+ }
1027
+ function createNullSink() {
1028
+ return {
1029
+ record: () => {}
1030
+ };
1031
+ }
1032
+
1033
+ // src/plugin/hooks.ts
1034
+ var codeSignals = [
1035
+ "```",
1036
+ "function ",
1037
+ "class ",
1038
+ "const ",
1039
+ "let ",
1040
+ "import ",
1041
+ "export ",
1042
+ "diff --git"
1043
+ ];
1044
+ var sensitiveSignals = [
1045
+ "api key",
1046
+ "apikey",
1047
+ "token",
1048
+ "password",
1049
+ "secret",
1050
+ "private key",
1051
+ "confidential"
1052
+ ];
1053
+ function partText(part) {
1054
+ return part.type === "text" && typeof part.text === "string" ? part.text : "";
1055
+ }
1056
+ function partRequiresTools(part) {
1057
+ return typeof part.type === "string" && part.type.toLowerCase().includes("tool");
1058
+ }
1059
+ function promptText(output) {
1060
+ return output.parts.map(partText).join(`
1061
+ `);
1062
+ }
1063
+ function emitTelemetry(sink, record) {
1064
+ Promise.resolve().then(() => sink.record(record)).catch(() => {});
1065
+ }
1066
+ function deriveTaskSignals(input, output) {
1067
+ const prompt = promptText(output);
1068
+ const lowerPrompt = prompt.toLowerCase();
1069
+ const signals = {
1070
+ promptChars: prompt.length,
1071
+ hasCode: codeSignals.some((signal) => lowerPrompt.includes(signal))
1072
+ };
1073
+ if (sensitiveSignals.some((signal) => lowerPrompt.includes(signal))) {
1074
+ signals.privacySensitive = true;
1075
+ }
1076
+ if (output.parts.some(partRequiresTools)) {
1077
+ signals.requiresTools = true;
1078
+ }
1079
+ if (input.model !== undefined) {
1080
+ signals.explicitOverride = input.model;
1081
+ }
1082
+ return signals;
1083
+ }
1084
+ function createChatMessageHook(config, getAvailable, options = {}) {
1085
+ const sink = options.sink ?? createNullSink();
1086
+ const now = options.now ?? Date.now;
1087
+ return async (input, output) => {
1088
+ const signals = deriveTaskSignals(input, output);
1089
+ const decision2 = chooseModel({
1090
+ config,
1091
+ task: signals,
1092
+ availableModels: getAvailable()
1093
+ });
1094
+ output.message.model = decision2.selected;
1095
+ const telemetryContext = {
1096
+ ts: now(),
1097
+ promptHash: hashPrompt(promptText(output)),
1098
+ promptChars: signals.promptChars,
1099
+ sessionID: input.sessionID ?? output.message.sessionID
1100
+ };
1101
+ if (signals.estimatedInputTokens !== undefined) {
1102
+ telemetryContext.tokensIn = signals.estimatedInputTokens;
1103
+ }
1104
+ if (signals.estimatedOutputTokens !== undefined) {
1105
+ telemetryContext.tokensOut = signals.estimatedOutputTokens;
1106
+ }
1107
+ emitTelemetry(sink, toCostRecord(decision2, telemetryContext));
1108
+ };
1109
+ }
1110
+ function createHooks(config, getAvailable, options = {}) {
1111
+ return {
1112
+ "chat.message": createChatMessageHook(config, getAvailable, options)
1113
+ };
1114
+ }
1115
+
1116
+ // src/index.ts
1117
+ var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
1118
+ function createShellExec($) {
1119
+ return async (command, args) => {
1120
+ const output = await $`${command} ${[...args]}`.nothrow().quiet();
1121
+ return {
1122
+ exitCode: output.exitCode,
1123
+ stdout: output.stdout.toString(),
1124
+ stderr: output.stderr.toString()
1125
+ };
1126
+ };
1127
+ }
1128
+ function logAvailabilityRefreshError(error) {
1129
+ const message = error instanceof Error ? error.message : String(error);
1130
+ console.warn(`[openteam] availability refresh failed: ${message}`);
1131
+ }
1132
+ function logTelemetryError(error) {
1133
+ const message = error instanceof Error ? error.message : String(error);
1134
+ console.warn(`[openteam] telemetry write failed: ${message}`);
1135
+ }
1136
+ function telemetryOptions(rawOptions) {
1137
+ const telemetry = rawOptions !== null && typeof rawOptions === "object" && "telemetry" in rawOptions && rawOptions.telemetry !== null && typeof rawOptions.telemetry === "object" ? rawOptions.telemetry : {};
1138
+ const enabled = !("enabled" in telemetry) || telemetry.enabled !== false;
1139
+ const configuredPath = "path" in telemetry && typeof telemetry.path === "string" ? telemetry.path : DEFAULT_TELEMETRY_PATH;
1140
+ return {
1141
+ enabled,
1142
+ path: configuredPath
1143
+ };
1144
+ }
1145
+ function createFileAppender(filePath, deps = {}) {
1146
+ const mkdirFn = deps.mkdir ?? mkdir;
1147
+ const appendFileFn = deps.appendFile ?? appendFile;
1148
+ return async (line) => {
1149
+ await mkdirFn(dirname(filePath), { recursive: true });
1150
+ await appendFileFn(filePath, line, "utf8");
1151
+ };
1152
+ }
1153
+ function createTelemetrySink(rawOptions) {
1154
+ const options = telemetryOptions(rawOptions);
1155
+ if (!options.enabled) {
1156
+ return createNullSink();
1157
+ }
1158
+ return createJsonlSink({
1159
+ append: createFileAppender(options.path),
1160
+ onError: logTelemetryError
1161
+ });
1162
+ }
1163
+ var server = async (ctx, rawOptions) => {
1164
+ const config = loadOpenTeamConfig(rawOptions ?? {});
1165
+ const registry = new RuntimeRegistry({
1166
+ fetch: globalThis.fetch,
1167
+ clock: () => new Date().toISOString(),
1168
+ foundryDiscovery: foundryDiscoveryFromExec(createShellExec(ctx.$))
1169
+ });
1170
+ const cache = createAvailabilityCache({
1171
+ config,
1172
+ registry,
1173
+ onRefreshError: logAvailabilityRefreshError
1174
+ });
1175
+ cache.refresh();
1176
+ const hooks = createHooks(config, cache.get, {
1177
+ sink: createTelemetrySink(rawOptions)
1178
+ });
1179
+ return {
1180
+ ...hooks,
1181
+ event: async ({ event }) => {
1182
+ if (event.type === "session.created" || event.type === "session.idle") {
1183
+ cache.refresh();
1184
+ }
1185
+ }
1186
+ };
1187
+ };
1188
+ var plugin = {
1189
+ id: "openteam",
1190
+ server
1191
+ };
1192
+ var src_default = plugin;
1193
+ export {
1194
+ server,
1195
+ logTelemetryError,
1196
+ logAvailabilityRefreshError,
1197
+ src_default as default,
1198
+ createFileAppender
1199
+ };