@jmanuelcorral/openteam 0.1.1 → 0.1.2
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/.opencode/command/openteam.md +15 -0
- package/README.md +44 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +883 -0
- package/dist/commands/baseline.d.ts +12 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/dispatch.d.ts +17 -0
- package/dist/commands/dispatch.d.ts.map +1 -0
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/report.d.ts +22 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/types.d.ts +6 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/config/persist.d.ts +14 -0
- package/dist/config/persist.d.ts.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +407 -6
- package/dist/plugin/commandTool.d.ts +10 -0
- package/dist/plugin/commandTool.d.ts.map +1 -0
- package/dist/telemetry/read.d.ts +14 -0
- package/dist/telemetry/read.d.ts.map +1 -0
- package/package.json +8 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OpenTeamConfig } from "../config/schema";
|
|
2
|
+
import { type ModelRef } from "../config/schema";
|
|
3
|
+
import type { CommandOutcome } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Parse a `provider/model` string into a validated {@link ModelRef}.
|
|
6
|
+
* Splits on the first `/` so model IDs may themselves contain `/` or `:`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseModelRef(input: string): ModelRef;
|
|
9
|
+
export declare function showBaseline(config: OpenTeamConfig): CommandOutcome;
|
|
10
|
+
export declare function setBaseline(config: OpenTeamConfig, ref: ModelRef): CommandOutcome;
|
|
11
|
+
export declare function autoBaseline(config: OpenTeamConfig): CommandOutcome;
|
|
12
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,KAAK,QAAQ,EAAkB,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAiB9C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAcrD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAUnE;AAED,wBAAgB,WAAW,CACzB,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,QAAQ,GACZ,cAAc,CAchB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAcnE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OpenTeamConfig } from "../config/schema";
|
|
2
|
+
import type { LocalRuntimeSnapshot } from "../local/types";
|
|
3
|
+
import type { CostRecord } from "../telemetry/types";
|
|
4
|
+
export type CliDeps = {
|
|
5
|
+
loadConfig: (path: string) => Promise<OpenTeamConfig>;
|
|
6
|
+
saveConfig: (config: OpenTeamConfig, path: string) => Promise<void>;
|
|
7
|
+
probe: (config: OpenTeamConfig) => Promise<LocalRuntimeSnapshot[]>;
|
|
8
|
+
readTelemetry: (path: string) => Promise<CostRecord[]>;
|
|
9
|
+
configPath: string;
|
|
10
|
+
telemetryPath: string;
|
|
11
|
+
};
|
|
12
|
+
export type CliResult = {
|
|
13
|
+
exitCode: number;
|
|
14
|
+
stdout: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function runCli(argv: readonly string[], deps: CliDeps): Promise<CliResult>;
|
|
17
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../src/commands/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAUrD,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACtD,UAAU,EAAE,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,KAAK,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA4FF,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,SAAS,CAAC,CAgDpB"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OpenTeamConfig } from "../config/schema";
|
|
2
|
+
import type { LocalRuntimeSnapshot } from "../local/types";
|
|
3
|
+
export type DoctorInput = {
|
|
4
|
+
config: OpenTeamConfig;
|
|
5
|
+
snapshots: readonly LocalRuntimeSnapshot[];
|
|
6
|
+
telemetryPath: string;
|
|
7
|
+
telemetryRecords: number;
|
|
8
|
+
};
|
|
9
|
+
export declare function renderDoctor(input: DoctorInput): string;
|
|
10
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,SAAS,oBAAoB,EAAE,CAAC;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B,CAAC;AAWF,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAkCvD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ComplexityTier } from "../capabilities/types";
|
|
2
|
+
import type { CostRecord } from "../telemetry/types";
|
|
3
|
+
export type TierSummary = {
|
|
4
|
+
count: number;
|
|
5
|
+
estimatedUSD: number;
|
|
6
|
+
savingsUSD: number;
|
|
7
|
+
};
|
|
8
|
+
export type CostReport = {
|
|
9
|
+
count: number;
|
|
10
|
+
localCount: number;
|
|
11
|
+
frontierCount: number;
|
|
12
|
+
totalEstimatedUSD: number;
|
|
13
|
+
totalBaselineUSD: number;
|
|
14
|
+
totalSavingsUSD: number;
|
|
15
|
+
savingsPct: number;
|
|
16
|
+
tokensIn: number;
|
|
17
|
+
tokensOut: number;
|
|
18
|
+
byTier: Record<ComplexityTier, TierSummary>;
|
|
19
|
+
};
|
|
20
|
+
export declare function summarizeCostRecords(records: readonly CostRecord[]): CostReport;
|
|
21
|
+
export declare function renderReport(report: CostReport): string;
|
|
22
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/commands/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AASrD,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;CAC7C,CAAC;AAWF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,UAAU,CAuCZ;AAMD,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAuBvD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/commands/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAEhB,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { type OpenTeamConfig } from "./schema";
|
|
3
|
+
export declare const DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
|
|
4
|
+
/**
|
|
5
|
+
* Validate and serialize a config to canonical JSON (2-space indent, trailing newline).
|
|
6
|
+
* Pure: no filesystem access.
|
|
7
|
+
*/
|
|
8
|
+
export declare function serializeOpenTeamConfig(config: OpenTeamConfig): string;
|
|
9
|
+
export type WriteConfigDeps = {
|
|
10
|
+
mkdir?: typeof mkdir;
|
|
11
|
+
writeFile?: typeof writeFile;
|
|
12
|
+
};
|
|
13
|
+
export declare function writeOpenTeamConfigFile(config: OpenTeamConfig, path?: string, deps?: WriteConfigDeps): Promise<void>;
|
|
14
|
+
//# sourceMappingURL=persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/config/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EAAE,KAAK,cAAc,EAAwB,MAAM,UAAU,CAAC;AAErE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAGtE;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,SAAS,CAAC;CAC9B,CAAC;AAEF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,IAAI,SAAsB,EAC1B,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAMf"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import type { Plugin, PluginModule } from "@opencode-ai/plugin";
|
|
3
|
+
import type { CliDeps } from "./commands/dispatch";
|
|
4
|
+
import type { OpenTeamConfig } from "./config/schema";
|
|
5
|
+
import { RuntimeRegistry } from "./local/registry";
|
|
3
6
|
type FileAppenderDeps = {
|
|
4
7
|
mkdir?: typeof mkdir;
|
|
5
8
|
appendFile?: typeof appendFile;
|
|
@@ -7,6 +10,8 @@ type FileAppenderDeps = {
|
|
|
7
10
|
export declare function logAvailabilityRefreshError(error: unknown): void;
|
|
8
11
|
export declare function logTelemetryError(error: unknown): void;
|
|
9
12
|
export declare function createFileAppender(filePath: string, deps?: FileAppenderDeps): (line: string) => Promise<void>;
|
|
13
|
+
export declare function isMissingFile(error: unknown): boolean;
|
|
14
|
+
export declare function createCliDeps(config: OpenTeamConfig, registry: RuntimeRegistry, telemetryPath: string): CliDeps;
|
|
10
15
|
export declare const server: Plugin;
|
|
11
16
|
declare const plugin: PluginModule;
|
|
12
17
|
export default plugin;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAY,MAAM,kBAAkB,CAAC;AAG/D,OAAO,KAAK,EAAE,MAAM,EAAe,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgBnD,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;CAChC,CAAC;AAcF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGhE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGtD;AA0BD,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,gBAAqB,GAC1B,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAQjC;AAeD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOrD;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,eAAe,EACzB,aAAa,EAAE,MAAM,GACpB,OAAO,CAkBT;AAED,eAAO,MAAM,MAAM,EAAE,MA8BpB,CAAC;AAEF,QAAA,MAAM,MAAM,EAAE,YAGb,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { appendFile, mkdir } from "node:fs/promises";
|
|
3
|
-
import { dirname } from "node:path";
|
|
2
|
+
import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "node:fs/promises";
|
|
3
|
+
import { dirname as dirname2 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/config/schema.ts
|
|
6
6
|
import { z } from "zod";
|
|
@@ -84,6 +84,22 @@ function loadOpenTeamConfig(raw) {
|
|
|
84
84
|
return OpenTeamConfigSchema.parse(raw ?? {});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// src/config/persist.ts
|
|
88
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
89
|
+
import { dirname } from "node:path";
|
|
90
|
+
var DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
|
|
91
|
+
function serializeOpenTeamConfig(config) {
|
|
92
|
+
const validated = OpenTeamConfigSchema.parse(config);
|
|
93
|
+
return `${JSON.stringify(validated, null, 2)}
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, deps = {}) {
|
|
97
|
+
const mkdirFn = deps.mkdir ?? mkdir;
|
|
98
|
+
const writeFileFn = deps.writeFile ?? writeFile;
|
|
99
|
+
await mkdirFn(dirname(path), { recursive: true });
|
|
100
|
+
await writeFileFn(path, serializeOpenTeamConfig(config), "utf8");
|
|
101
|
+
}
|
|
102
|
+
|
|
87
103
|
// src/local/openai-compatible.ts
|
|
88
104
|
function normalizeBaseURL(baseURL) {
|
|
89
105
|
return baseURL.replace(/\/+$/, "");
|
|
@@ -520,6 +536,323 @@ function createAvailabilityCache(options) {
|
|
|
520
536
|
};
|
|
521
537
|
}
|
|
522
538
|
|
|
539
|
+
// src/plugin/commandTool.ts
|
|
540
|
+
import { tool } from "@opencode-ai/plugin";
|
|
541
|
+
|
|
542
|
+
// src/commands/baseline.ts
|
|
543
|
+
function formatRef(ref) {
|
|
544
|
+
return ref === null ? "—" : `${ref.providerID}/${ref.modelID}`;
|
|
545
|
+
}
|
|
546
|
+
function effectiveBaseline(config) {
|
|
547
|
+
if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
|
|
548
|
+
return `${formatRef(config.baseline.pinnedModel)} (pinned)`;
|
|
549
|
+
}
|
|
550
|
+
return "cheapest-capable (auto)";
|
|
551
|
+
}
|
|
552
|
+
function parseModelRef(input) {
|
|
553
|
+
const trimmed = input.trim();
|
|
554
|
+
const slash = trimmed.indexOf("/");
|
|
555
|
+
if (slash <= 0 || slash === trimmed.length - 1) {
|
|
556
|
+
throw new Error(`Modelo inválido "${input}". Usa el formato provider/model, p.ej. anthropic/claude-sonnet-4-5.`);
|
|
557
|
+
}
|
|
558
|
+
return ModelRefSchema.parse({
|
|
559
|
+
providerID: trimmed.slice(0, slash),
|
|
560
|
+
modelID: trimmed.slice(slash + 1)
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
function showBaseline(config) {
|
|
564
|
+
const lines = [
|
|
565
|
+
"openteam baseline:",
|
|
566
|
+
` modo: ${config.baseline.mode}`,
|
|
567
|
+
` pinned: ${formatRef(config.baseline.pinnedModel)}`,
|
|
568
|
+
` hardDefault: ${formatRef(config.baseline.hardDefault)}`,
|
|
569
|
+
` efectivo: ${effectiveBaseline(config)}`
|
|
570
|
+
];
|
|
571
|
+
return { message: lines.join(`
|
|
572
|
+
`) };
|
|
573
|
+
}
|
|
574
|
+
function setBaseline(config, ref) {
|
|
575
|
+
const next = {
|
|
576
|
+
...config,
|
|
577
|
+
baseline: {
|
|
578
|
+
...config.baseline,
|
|
579
|
+
mode: "pinned",
|
|
580
|
+
pinnedModel: ref
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
return {
|
|
584
|
+
config: next,
|
|
585
|
+
message: `Baseline fijado a ${formatRef(ref)} (modo pinned).`
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function autoBaseline(config) {
|
|
589
|
+
const next = {
|
|
590
|
+
...config,
|
|
591
|
+
baseline: {
|
|
592
|
+
...config.baseline,
|
|
593
|
+
mode: "auto",
|
|
594
|
+
pinnedModel: null
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
return {
|
|
598
|
+
config: next,
|
|
599
|
+
message: "Baseline en modo auto (cheapest-capable)."
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/commands/doctor.ts
|
|
604
|
+
function runtimeLine(snapshot) {
|
|
605
|
+
const mark = snapshot.reachable ? "✓" : "✗";
|
|
606
|
+
const detail = snapshot.reachable ? `${snapshot.models.length} modelo(s)` : snapshot.error ?? "inalcanzable";
|
|
607
|
+
return ` ${mark} ${snapshot.id.padEnd(14)} ${snapshot.baseURL || "(sin baseURL)"} — ${detail}`;
|
|
608
|
+
}
|
|
609
|
+
function renderDoctor(input) {
|
|
610
|
+
const enabledRuntimes = input.config.local.runtimes.filter((r) => r.enabled);
|
|
611
|
+
const reachable = input.snapshots.filter((s) => s.reachable).length;
|
|
612
|
+
const lines = [
|
|
613
|
+
"openteam doctor:",
|
|
614
|
+
` baseline: ${input.config.baseline.mode}` + (input.config.baseline.mode === "pinned" && input.config.baseline.pinnedModel !== null ? ` (${input.config.baseline.pinnedModel.providerID}/${input.config.baseline.pinnedModel.modelID})` : ""),
|
|
615
|
+
` privacidad: ${input.config.privacyMode}`,
|
|
616
|
+
` runtimes locales (${reachable}/${enabledRuntimes.length} alcanzables):`
|
|
617
|
+
];
|
|
618
|
+
if (input.snapshots.length === 0) {
|
|
619
|
+
lines.push(" (ningún runtime habilitado)");
|
|
620
|
+
} else {
|
|
621
|
+
for (const snapshot of input.snapshots) {
|
|
622
|
+
lines.push(runtimeLine(snapshot));
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
lines.push(` telemetría: ${input.telemetryPath} — ${input.telemetryRecords} registro(s)`);
|
|
626
|
+
if (reachable === 0) {
|
|
627
|
+
lines.push(" ⚠ Ningún runtime local alcanzable: todo el tráfico irá a frontier.");
|
|
628
|
+
}
|
|
629
|
+
return lines.join(`
|
|
630
|
+
`);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/commands/report.ts
|
|
634
|
+
var TIERS = [
|
|
635
|
+
"trivial",
|
|
636
|
+
"simple",
|
|
637
|
+
"moderate",
|
|
638
|
+
"hard"
|
|
639
|
+
];
|
|
640
|
+
function emptyTierSummary() {
|
|
641
|
+
return {
|
|
642
|
+
trivial: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
|
|
643
|
+
simple: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
|
|
644
|
+
moderate: { count: 0, estimatedUSD: 0, savingsUSD: 0 },
|
|
645
|
+
hard: { count: 0, estimatedUSD: 0, savingsUSD: 0 }
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function summarizeCostRecords(records) {
|
|
649
|
+
const report = {
|
|
650
|
+
count: 0,
|
|
651
|
+
localCount: 0,
|
|
652
|
+
frontierCount: 0,
|
|
653
|
+
totalEstimatedUSD: 0,
|
|
654
|
+
totalBaselineUSD: 0,
|
|
655
|
+
totalSavingsUSD: 0,
|
|
656
|
+
savingsPct: 0,
|
|
657
|
+
tokensIn: 0,
|
|
658
|
+
tokensOut: 0,
|
|
659
|
+
byTier: emptyTierSummary()
|
|
660
|
+
};
|
|
661
|
+
for (const record of records) {
|
|
662
|
+
report.count += 1;
|
|
663
|
+
if (record.routeKind === "local") {
|
|
664
|
+
report.localCount += 1;
|
|
665
|
+
} else {
|
|
666
|
+
report.frontierCount += 1;
|
|
667
|
+
}
|
|
668
|
+
report.totalEstimatedUSD += record.estimatedCostUSD;
|
|
669
|
+
report.totalBaselineUSD += record.baselineCostUSD;
|
|
670
|
+
report.totalSavingsUSD += record.estimatedSavingsUSD;
|
|
671
|
+
report.tokensIn += record.tokensIn ?? 0;
|
|
672
|
+
report.tokensOut += record.tokensOut ?? 0;
|
|
673
|
+
const tier = report.byTier[record.tier];
|
|
674
|
+
tier.count += 1;
|
|
675
|
+
tier.estimatedUSD += record.estimatedCostUSD;
|
|
676
|
+
tier.savingsUSD += record.estimatedSavingsUSD;
|
|
677
|
+
}
|
|
678
|
+
report.savingsPct = report.totalBaselineUSD > 0 ? report.totalSavingsUSD / report.totalBaselineUSD * 100 : 0;
|
|
679
|
+
return report;
|
|
680
|
+
}
|
|
681
|
+
function usd(value) {
|
|
682
|
+
return `$${value.toFixed(5)}`;
|
|
683
|
+
}
|
|
684
|
+
function renderReport(report) {
|
|
685
|
+
if (report.count === 0) {
|
|
686
|
+
return "openteam report: sin registros de telemetría todavía.";
|
|
687
|
+
}
|
|
688
|
+
const lines = [
|
|
689
|
+
"openteam report:",
|
|
690
|
+
` decisiones: ${report.count} (local ${report.localCount} · frontier ${report.frontierCount})`,
|
|
691
|
+
` coste real: ${usd(report.totalEstimatedUSD)}`,
|
|
692
|
+
` baseline: ${usd(report.totalBaselineUSD)}`,
|
|
693
|
+
` ahorro: ${usd(report.totalSavingsUSD)} (${report.savingsPct.toFixed(2)}%)`,
|
|
694
|
+
` tokens: in ${report.tokensIn} · out ${report.tokensOut}`,
|
|
695
|
+
" por tier:"
|
|
696
|
+
];
|
|
697
|
+
for (const tier of TIERS) {
|
|
698
|
+
const summary = report.byTier[tier];
|
|
699
|
+
lines.push(` ${tier.padEnd(9)} ${summary.count} · coste ${usd(summary.estimatedUSD)} · ahorro ${usd(summary.savingsUSD)}`);
|
|
700
|
+
}
|
|
701
|
+
return lines.join(`
|
|
702
|
+
`);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/commands/dispatch.ts
|
|
706
|
+
var HELP = [
|
|
707
|
+
"openteam — comandos runtime",
|
|
708
|
+
"",
|
|
709
|
+
"Uso:",
|
|
710
|
+
" openteam baseline show Muestra el baseline efectivo",
|
|
711
|
+
" openteam baseline set <p/model> Fija el baseline (modo pinned)",
|
|
712
|
+
" openteam baseline auto Baseline cheapest-capable (modo auto)",
|
|
713
|
+
" openteam doctor Diagnóstico de runtimes y config",
|
|
714
|
+
" openteam report Resumen de coste/ahorro (telemetría)",
|
|
715
|
+
"",
|
|
716
|
+
"Opciones:",
|
|
717
|
+
" --config <path> Ruta de .opencode/openteam.json",
|
|
718
|
+
" --telemetry <path> Ruta del JSONL de telemetría"
|
|
719
|
+
].join(`
|
|
720
|
+
`);
|
|
721
|
+
function parseArgs(argv) {
|
|
722
|
+
const positionals = [];
|
|
723
|
+
const result = { positionals };
|
|
724
|
+
for (let i = 0;i < argv.length; i += 1) {
|
|
725
|
+
const arg = argv[i];
|
|
726
|
+
if (arg === "--config") {
|
|
727
|
+
const value = argv[i + 1];
|
|
728
|
+
if (value !== undefined) {
|
|
729
|
+
result.configPath = value;
|
|
730
|
+
}
|
|
731
|
+
i += 1;
|
|
732
|
+
} else if (arg === "--telemetry") {
|
|
733
|
+
const value = argv[i + 1];
|
|
734
|
+
if (value !== undefined) {
|
|
735
|
+
result.telemetryPath = value;
|
|
736
|
+
}
|
|
737
|
+
i += 1;
|
|
738
|
+
} else if (arg !== undefined) {
|
|
739
|
+
positionals.push(arg);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return result;
|
|
743
|
+
}
|
|
744
|
+
async function runBaseline(positionals, deps, configPath) {
|
|
745
|
+
const sub = positionals[1];
|
|
746
|
+
if (sub === "show" || sub === undefined) {
|
|
747
|
+
const config = await deps.loadConfig(configPath);
|
|
748
|
+
return { exitCode: 0, stdout: showBaseline(config).message };
|
|
749
|
+
}
|
|
750
|
+
if (sub === "auto") {
|
|
751
|
+
const config = await deps.loadConfig(configPath);
|
|
752
|
+
const outcome = autoBaseline(config);
|
|
753
|
+
if (outcome.config !== undefined) {
|
|
754
|
+
await deps.saveConfig(outcome.config, configPath);
|
|
755
|
+
}
|
|
756
|
+
return { exitCode: 0, stdout: outcome.message };
|
|
757
|
+
}
|
|
758
|
+
if (sub === "set") {
|
|
759
|
+
const target = positionals[2];
|
|
760
|
+
if (target === undefined) {
|
|
761
|
+
return {
|
|
762
|
+
exitCode: 1,
|
|
763
|
+
stdout: "Falta el modelo. Uso: openteam baseline set <provider/model>"
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
const config = await deps.loadConfig(configPath);
|
|
767
|
+
const outcome = setBaseline(config, parseModelRef(target));
|
|
768
|
+
if (outcome.config !== undefined) {
|
|
769
|
+
await deps.saveConfig(outcome.config, configPath);
|
|
770
|
+
}
|
|
771
|
+
return { exitCode: 0, stdout: outcome.message };
|
|
772
|
+
}
|
|
773
|
+
return {
|
|
774
|
+
exitCode: 1,
|
|
775
|
+
stdout: `Subcomando de baseline desconocido: ${sub}
|
|
776
|
+
|
|
777
|
+
${HELP}`
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
async function runCli(argv, deps) {
|
|
781
|
+
const parsed = parseArgs(argv);
|
|
782
|
+
const command = parsed.positionals[0];
|
|
783
|
+
const configPath = parsed.configPath ?? deps.configPath;
|
|
784
|
+
const telemetryPath = parsed.telemetryPath ?? deps.telemetryPath;
|
|
785
|
+
try {
|
|
786
|
+
if (command === "baseline") {
|
|
787
|
+
return await runBaseline(parsed.positionals, deps, configPath);
|
|
788
|
+
}
|
|
789
|
+
if (command === "doctor") {
|
|
790
|
+
const config = await deps.loadConfig(configPath);
|
|
791
|
+
const [snapshots, records] = await Promise.all([
|
|
792
|
+
deps.probe(config),
|
|
793
|
+
deps.readTelemetry(telemetryPath)
|
|
794
|
+
]);
|
|
795
|
+
return {
|
|
796
|
+
exitCode: 0,
|
|
797
|
+
stdout: renderDoctor({
|
|
798
|
+
config,
|
|
799
|
+
snapshots,
|
|
800
|
+
telemetryPath,
|
|
801
|
+
telemetryRecords: records.length
|
|
802
|
+
})
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
if (command === "report") {
|
|
806
|
+
const records = await deps.readTelemetry(telemetryPath);
|
|
807
|
+
return {
|
|
808
|
+
exitCode: 0,
|
|
809
|
+
stdout: renderReport(summarizeCostRecords(records))
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
if (command === undefined || command === "help" || command === "--help") {
|
|
813
|
+
return { exitCode: 0, stdout: HELP };
|
|
814
|
+
}
|
|
815
|
+
return {
|
|
816
|
+
exitCode: 1,
|
|
817
|
+
stdout: `Comando desconocido: ${command}
|
|
818
|
+
|
|
819
|
+
${HELP}`
|
|
820
|
+
};
|
|
821
|
+
} catch (error) {
|
|
822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
823
|
+
return { exitCode: 1, stdout: `Error: ${message}` };
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/plugin/commandTool.ts
|
|
828
|
+
function commandArgv(action, model) {
|
|
829
|
+
switch (action) {
|
|
830
|
+
case "show":
|
|
831
|
+
return ["baseline", "show"];
|
|
832
|
+
case "auto":
|
|
833
|
+
return ["baseline", "auto"];
|
|
834
|
+
case "set":
|
|
835
|
+
return model === undefined ? ["baseline", "set"] : ["baseline", "set", model];
|
|
836
|
+
case "doctor":
|
|
837
|
+
return ["doctor"];
|
|
838
|
+
case "report":
|
|
839
|
+
return ["report"];
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function createCommandTool(deps) {
|
|
843
|
+
return tool({
|
|
844
|
+
description: "Comandos runtime de openteam: baseline (show/set/auto), doctor y report.",
|
|
845
|
+
args: {
|
|
846
|
+
action: tool.schema.enum(["show", "set", "auto", "doctor", "report"]).describe("Acción a ejecutar"),
|
|
847
|
+
model: tool.schema.string().optional().describe("Modelo provider/model (solo para action=set)")
|
|
848
|
+
},
|
|
849
|
+
async execute(args) {
|
|
850
|
+
const result = await runCli(commandArgv(args.action, args.model), deps);
|
|
851
|
+
return result.stdout;
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
523
856
|
// src/capabilities/budget.ts
|
|
524
857
|
var WARNING_UTILIZATION = 0.8;
|
|
525
858
|
function projectedSpend(current, estimatedCostUSD) {
|
|
@@ -1113,8 +1446,47 @@ function createHooks(config, getAvailable, options = {}) {
|
|
|
1113
1446
|
};
|
|
1114
1447
|
}
|
|
1115
1448
|
|
|
1116
|
-
// src/
|
|
1449
|
+
// src/telemetry/read.ts
|
|
1450
|
+
import { readFile } from "node:fs/promises";
|
|
1117
1451
|
var DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
|
|
1452
|
+
function parseCostRecordsJsonl(text) {
|
|
1453
|
+
const records = [];
|
|
1454
|
+
for (const line of text.split(`
|
|
1455
|
+
`)) {
|
|
1456
|
+
const trimmed = line.trim();
|
|
1457
|
+
if (trimmed.length === 0) {
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
let candidate;
|
|
1461
|
+
try {
|
|
1462
|
+
candidate = JSON.parse(trimmed);
|
|
1463
|
+
} catch {
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
const parsed = CostRecordSchema.safeParse(candidate);
|
|
1467
|
+
if (parsed.success) {
|
|
1468
|
+
records.push(parsed.data);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
return records;
|
|
1472
|
+
}
|
|
1473
|
+
function isMissingFile(error) {
|
|
1474
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1475
|
+
}
|
|
1476
|
+
async function readCostRecords(path, deps = {}) {
|
|
1477
|
+
const readFileFn = deps.readFile ?? readFile;
|
|
1478
|
+
try {
|
|
1479
|
+
const content = await readFileFn(path, "utf8");
|
|
1480
|
+
return parseCostRecordsJsonl(content);
|
|
1481
|
+
} catch (error) {
|
|
1482
|
+
if (isMissingFile(error)) {
|
|
1483
|
+
return [];
|
|
1484
|
+
}
|
|
1485
|
+
throw error;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
// src/index.ts
|
|
1118
1490
|
function createShellExec($) {
|
|
1119
1491
|
return async (command, args) => {
|
|
1120
1492
|
const output = await $`${command} ${[...args]}`.nothrow().quiet();
|
|
@@ -1143,10 +1515,10 @@ function telemetryOptions(rawOptions) {
|
|
|
1143
1515
|
};
|
|
1144
1516
|
}
|
|
1145
1517
|
function createFileAppender(filePath, deps = {}) {
|
|
1146
|
-
const mkdirFn = deps.mkdir ??
|
|
1518
|
+
const mkdirFn = deps.mkdir ?? mkdir2;
|
|
1147
1519
|
const appendFileFn = deps.appendFile ?? appendFile;
|
|
1148
1520
|
return async (line) => {
|
|
1149
|
-
await mkdirFn(
|
|
1521
|
+
await mkdirFn(dirname2(filePath), { recursive: true });
|
|
1150
1522
|
await appendFileFn(filePath, line, "utf8");
|
|
1151
1523
|
};
|
|
1152
1524
|
}
|
|
@@ -1160,6 +1532,28 @@ function createTelemetrySink(rawOptions) {
|
|
|
1160
1532
|
onError: logTelemetryError
|
|
1161
1533
|
});
|
|
1162
1534
|
}
|
|
1535
|
+
function isMissingFile2(error) {
|
|
1536
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1537
|
+
}
|
|
1538
|
+
function createCliDeps(config, registry, telemetryPath) {
|
|
1539
|
+
return {
|
|
1540
|
+
loadConfig: async (path) => {
|
|
1541
|
+
try {
|
|
1542
|
+
return loadOpenTeamConfig(JSON.parse(await readFile2(path, "utf8")));
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
if (isMissingFile2(error)) {
|
|
1545
|
+
return config;
|
|
1546
|
+
}
|
|
1547
|
+
throw error;
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
saveConfig: (nextConfig, path) => writeOpenTeamConfigFile(nextConfig, path),
|
|
1551
|
+
probe: (nextConfig) => registry.probe(nextConfig.local.runtimes),
|
|
1552
|
+
readTelemetry: (path) => readCostRecords(path),
|
|
1553
|
+
configPath: DEFAULT_CONFIG_PATH,
|
|
1554
|
+
telemetryPath
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1163
1557
|
var server = async (ctx, rawOptions) => {
|
|
1164
1558
|
const config = loadOpenTeamConfig(rawOptions ?? {});
|
|
1165
1559
|
const registry = new RuntimeRegistry({
|
|
@@ -1173,11 +1567,16 @@ var server = async (ctx, rawOptions) => {
|
|
|
1173
1567
|
onRefreshError: logAvailabilityRefreshError
|
|
1174
1568
|
});
|
|
1175
1569
|
cache.refresh();
|
|
1570
|
+
const telemetryPath = telemetryOptions(rawOptions).path;
|
|
1176
1571
|
const hooks = createHooks(config, cache.get, {
|
|
1177
1572
|
sink: createTelemetrySink(rawOptions)
|
|
1178
1573
|
});
|
|
1574
|
+
const cliDeps = createCliDeps(config, registry, telemetryPath);
|
|
1179
1575
|
return {
|
|
1180
1576
|
...hooks,
|
|
1577
|
+
tool: {
|
|
1578
|
+
openteam: createCommandTool(cliDeps)
|
|
1579
|
+
},
|
|
1181
1580
|
event: async ({ event }) => {
|
|
1182
1581
|
if (event.type === "session.created" || event.type === "session.idle") {
|
|
1183
1582
|
cache.refresh();
|
|
@@ -1194,6 +1593,8 @@ export {
|
|
|
1194
1593
|
server,
|
|
1195
1594
|
logTelemetryError,
|
|
1196
1595
|
logAvailabilityRefreshError,
|
|
1596
|
+
isMissingFile2 as isMissingFile,
|
|
1197
1597
|
src_default as default,
|
|
1198
|
-
createFileAppender
|
|
1598
|
+
createFileAppender,
|
|
1599
|
+
createCliDeps
|
|
1199
1600
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import { type CliDeps } from "../commands/dispatch";
|
|
3
|
+
export type CommandAction = "show" | "set" | "auto" | "doctor" | "report";
|
|
4
|
+
/**
|
|
5
|
+
* Map a tool action (+ optional model) to the argv understood by {@link runCli}.
|
|
6
|
+
* Pure so it can be unit-tested without the opencode runtime.
|
|
7
|
+
*/
|
|
8
|
+
export declare function commandArgv(action: CommandAction, model?: string): string[];
|
|
9
|
+
export declare function createCommandTool(deps: CliDeps): ToolDefinition;
|
|
10
|
+
//# sourceMappingURL=commandTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandTool.d.ts","sourceRoot":"","sources":["../../src/plugin/commandTool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,sBAAsB,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAe3E;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,CAkB/D"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { type CostRecord } from "./types";
|
|
3
|
+
export declare const DEFAULT_TELEMETRY_PATH = ".opencode/openteam-telemetry.jsonl";
|
|
4
|
+
/**
|
|
5
|
+
* Parse newline-delimited JSON cost records.
|
|
6
|
+
* Pure: silently skips blank lines and lines that fail schema validation,
|
|
7
|
+
* so a partially corrupted telemetry file never throws.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseCostRecordsJsonl(text: string): CostRecord[];
|
|
10
|
+
export type ReadCostRecordsDeps = {
|
|
11
|
+
readFile?: typeof readFile;
|
|
12
|
+
};
|
|
13
|
+
export declare function readCostRecords(path: string, deps?: ReadCostRecordsDeps): Promise<CostRecord[]>;
|
|
14
|
+
//# sourceMappingURL=read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/telemetry/read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,KAAK,UAAU,EAAoB,MAAM,SAAS,CAAC;AAE5D,eAAO,MAAM,sBAAsB,uCAAuC,CAAC;AAE3E;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAuBhE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,OAAO,QAAQ,CAAC;CAC5B,CAAC;AAWF,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,mBAAwB,GAC7B,OAAO,CAAC,UAAU,EAAE,CAAC,CAYvB"}
|