@zenera/cli 1.1.24 → 1.1.26
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/README.md +65 -0
- package/dist/ask.js +28 -5
- package/dist/banner.js +2 -2
- package/dist/batch.d.ts +6 -0
- package/dist/batch.js +76 -1
- package/dist/catalog.d.ts +31 -1
- package/dist/catalog.js +140 -7
- package/dist/commands/index.js +2 -0
- package/dist/commands/inspect.d.ts +52 -2
- package/dist/commands/inspect.js +220 -31
- package/dist/commands/memory.js +156 -32
- package/dist/commands/meta.js +333 -24
- package/dist/commands/models.js +97 -12
- package/dist/commands/open.js +4 -4
- package/dist/commands/run.js +38 -4
- package/dist/commands/sandbox.js +148 -16
- package/dist/commands/view.d.ts +8 -0
- package/dist/commands/view.js +114 -0
- package/dist/engine.d.ts +8 -1
- package/dist/engine.js +7 -0
- package/dist/eventlog.d.ts +64 -0
- package/dist/eventlog.js +170 -0
- package/dist/liveness.d.ts +7 -0
- package/dist/liveness.js +35 -3
- package/dist/main.js +0 -0
- package/dist/meta/editor.d.ts +9 -0
- package/dist/meta/editor.js +43 -0
- package/dist/meta/finetune/analyze-brief.d.ts +26 -0
- package/dist/meta/finetune/analyze-brief.js +131 -0
- package/dist/meta/finetune/apply-brief.d.ts +23 -0
- package/dist/meta/finetune/apply-brief.js +247 -0
- package/dist/meta/finetune/brief.d.ts +45 -0
- package/dist/meta/finetune/brief.js +158 -0
- package/dist/meta/finetune/children.d.ts +8 -0
- package/dist/meta/finetune/children.js +76 -0
- package/dist/meta/finetune/command.d.ts +30 -0
- package/dist/meta/finetune/command.js +516 -0
- package/dist/meta/finetune/cost.d.ts +46 -0
- package/dist/meta/finetune/cost.js +105 -0
- package/dist/meta/finetune/dataset/anchors.d.ts +41 -0
- package/dist/meta/finetune/dataset/anchors.js +239 -0
- package/dist/meta/finetune/dataset/changes.d.ts +36 -0
- package/dist/meta/finetune/dataset/changes.js +319 -0
- package/dist/meta/finetune/dataset/command.d.ts +17 -0
- package/dist/meta/finetune/dataset/command.js +1011 -0
- package/dist/meta/finetune/dataset/drift.d.ts +33 -0
- package/dist/meta/finetune/dataset/drift.js +71 -0
- package/dist/meta/finetune/dataset/export.d.ts +26 -0
- package/dist/meta/finetune/dataset/export.js +51 -0
- package/dist/meta/finetune/dataset/index.d.ts +2 -0
- package/dist/meta/finetune/dataset/index.js +2 -0
- package/dist/meta/finetune/dataset/sample.d.ts +50 -0
- package/dist/meta/finetune/dataset/sample.js +197 -0
- package/dist/meta/finetune/dataset/schema.d.ts +33 -0
- package/dist/meta/finetune/dataset/schema.js +216 -0
- package/dist/meta/finetune/dataset/store.d.ts +35 -0
- package/dist/meta/finetune/dataset/store.js +177 -0
- package/dist/meta/finetune/dataset/types.d.ts +127 -0
- package/dist/meta/finetune/dataset/types.js +39 -0
- package/dist/meta/finetune/failure.d.ts +15 -0
- package/dist/meta/finetune/failure.js +26 -0
- package/dist/meta/finetune/feedback.d.ts +57 -0
- package/dist/meta/finetune/feedback.js +104 -0
- package/dist/meta/finetune/guard.d.ts +29 -0
- package/dist/meta/finetune/guard.js +104 -0
- package/dist/meta/finetune/improvements.d.ts +98 -0
- package/dist/meta/finetune/improvements.js +501 -0
- package/dist/meta/finetune/index.d.ts +2 -0
- package/dist/meta/finetune/index.js +2 -0
- package/dist/meta/finetune/journal.d.ts +35 -0
- package/dist/meta/finetune/journal.js +135 -0
- package/dist/meta/finetune/live.d.ts +84 -0
- package/dist/meta/finetune/live.js +272 -0
- package/dist/meta/finetune/loop.d.ts +3 -0
- package/dist/meta/finetune/loop.js +163 -0
- package/dist/meta/finetune/memories.d.ts +47 -0
- package/dist/meta/finetune/memories.js +154 -0
- package/dist/meta/finetune/recheck.d.ts +58 -0
- package/dist/meta/finetune/recheck.js +311 -0
- package/dist/meta/finetune/report.d.ts +40 -0
- package/dist/meta/finetune/report.js +1397 -0
- package/dist/meta/finetune/retry.d.ts +24 -0
- package/dist/meta/finetune/retry.js +96 -0
- package/dist/meta/finetune/sampler.d.ts +18 -0
- package/dist/meta/finetune/sampler.js +47 -0
- package/dist/meta/finetune/system.d.ts +27 -0
- package/dist/meta/finetune/system.js +132 -0
- package/dist/meta/finetune/train.d.ts +43 -0
- package/dist/meta/finetune/train.js +425 -0
- package/dist/meta/finetune/tui.d.ts +16 -0
- package/dist/meta/finetune/tui.js +316 -0
- package/dist/meta/finetune/tuning.d.ts +193 -0
- package/dist/meta/finetune/tuning.js +308 -0
- package/dist/meta/finetune/usage.d.ts +66 -0
- package/dist/meta/finetune/usage.js +260 -0
- package/dist/meta/finetune/view.d.ts +9 -0
- package/dist/meta/finetune/view.js +27 -0
- package/dist/meta/host.d.ts +15 -0
- package/dist/meta/host.js +21 -0
- package/dist/meta/index.d.ts +11 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/launch.d.ts +81 -0
- package/dist/meta/launch.js +286 -0
- package/dist/meta/model.d.ts +44 -0
- package/dist/meta/model.js +119 -0
- package/dist/meta/prompts.d.ts +18 -0
- package/dist/meta/prompts.js +61 -0
- package/dist/meta/relay.d.ts +20 -0
- package/dist/meta/relay.js +123 -0
- package/dist/meta/tokens.d.ts +52 -0
- package/dist/meta/tokens.js +226 -0
- package/dist/meta/window.d.ts +61 -0
- package/dist/meta/window.js +198 -0
- package/dist/meta/wire.d.ts +42 -0
- package/dist/meta/wire.js +129 -0
- package/dist/meta.d.ts +71 -1
- package/dist/meta.js +303 -6
- package/dist/modelref.d.ts +16 -0
- package/dist/modelref.js +41 -0
- package/dist/narrate.js +5 -2
- package/dist/podman.d.ts +26 -0
- package/dist/podman.js +83 -3
- package/dist/projects.d.ts +0 -5
- package/dist/projects.js +2 -15
- package/dist/resources.d.ts +215 -0
- package/dist/resources.js +856 -0
- package/dist/sandbox.d.ts +2 -0
- package/dist/sandbox.js +10 -3
- package/dist/scaffold.d.ts +15 -7
- package/dist/scaffold.js +24 -67
- package/dist/session.js +4 -29
- package/dist/term.d.ts +2 -0
- package/dist/term.js +11 -2
- package/dist/trace.d.ts +5 -0
- package/dist/trace.js +107 -7
- package/dist/tui/app.js +19 -5
- package/dist/tui/links.d.ts +27 -0
- package/dist/tui/links.js +142 -0
- package/dist/tui/models.d.ts +35 -0
- package/dist/tui/models.js +447 -0
- package/dist/tui/sandbox.d.ts +10 -0
- package/dist/tui/sandbox.js +131 -0
- package/dist/tui/wrap.d.ts +5 -0
- package/dist/tui/wrap.js +12 -0
- package/dist/usage.d.ts +78 -0
- package/dist/usage.js +51 -0
- package/package.json +4 -2
- package/templates/editor/.github/copilot-instructions.md +60 -145
- package/templates/editor/.github/prompts/analyze.prompt.md +33 -0
- package/templates/editor/.github/prompts/dataset.prompt.md +108 -0
- package/templates/editor/.github/prompts/finetune-apply.prompt.md +270 -0
- package/templates/editor/.github/prompts/finetune.prompt.md +691 -198
- package/templates/editor/.github/prompts/new-agent.prompt.md +3 -1
- package/templates/editor/.github/prompts/new-skill.prompt.md +2 -0
- package/templates/editor/.github/prompts/project-review.prompt.md +5 -4
- package/templates/editor/.github/skills/zen-analyze-run/SKILL.md +588 -0
- package/templates/editor/.github/skills/zen-cli/SKILL.md +9 -1
- package/templates/editor/.github/skills/zen-cli/references/dataset.md +34 -0
- package/templates/editor/.github/skills/zen-cli/references/finetune.md +137 -0
- package/templates/editor/.github/skills/zen-cli/references/inspect.md +63 -11
- package/templates/editor/.github/skills/zen-cli/references/memory.md +33 -10
- package/templates/editor/.github/skills/zen-cli/references/meta.md +61 -0
- package/templates/editor/.github/skills/zen-cli/references/models.md +5 -1
- package/templates/editor/.github/skills/zen-cli/references/projects.md +4 -6
- package/templates/editor/.github/skills/zen-cli/references/rag.md +24 -6
- package/templates/editor/.github/skills/zen-cli/references/sandbox.md +35 -14
- package/templates/editor/.github/skills/zen-code-python/SKILL.md +123 -0
- package/templates/editor/.github/skills/zen-code-python/references/code_python.md +48 -0
- package/templates/editor/.github/skills/zen-dataset/SKILL.md +253 -0
- package/templates/editor/.github/skills/zen-finetune/SKILL.md +1492 -660
- package/templates/editor/.github/skills/zen-finetune/references/readme-template.md +268 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/batch.mjs +390 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/difficult.mjs +243 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/memory.mjs +280 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/next.mjs +398 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/report.mjs +797 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/select.mjs +354 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/usage.mjs +722 -0
- package/templates/editor/.github/skills/zen-inspect/SKILL.md +74 -26
- package/templates/editor/.github/skills/zen-inspect-ask/SKILL.md +271 -0
- package/templates/editor/.github/skills/zen-instructions/SKILL.md +4 -12
- package/templates/editor/.github/skills/zen-memory/SKILL.md +82 -53
- package/templates/editor/.github/skills/zen-rag-docs/SKILL.md +20 -8
- package/templates/editor/.github/skills/zen-review/SKILL.md +8 -31
- package/templates/editor/.github/skills/zen-review/scripts/check-paths.sh +5 -5
- package/templates/editor/.github/skills/zen-review/scripts/review.sh +3 -12
- package/templates/editor/.github/skills/zen-sandbox/SKILL.md +9 -4
- package/templates/editor/.github/skills/zen-sandbox-capacity/SKILL.md +2 -2
- package/templates/editor/.github/skills/zen-spec-sync/SKILL.md +2 -4
- package/templates/editor/.github/skills/zen-topology/SKILL.md +283 -0
- package/templates/editor/.vscode/settings.json +1 -0
- package/templates/project/agents/files-instructions.md +29 -20
- package/templates/project/agents/fork-instructions.md +78 -28
- package/templates/project/agents/memory-instructions.md +222 -494
- package/templates/project/agents/tools-instructions.md +14 -4
- package/templates/project/gitignore +2 -0
- package/templates/editor/.github/skills/zen-finetune/scripts/collect.sh +0 -432
- package/templates/editor/.github/skills/zen-finetune/scripts/sample.sh +0 -199
- package/templates/editor/.github/skills/zen-memory/scripts/check-instructions.sh +0 -116
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type KeyEntry, type KeyStore, type Provider } from './host.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Copilot offers its tools as OpenAI *custom* tools, which the completions API
|
|
4
|
+
* rejects outright — `400 Invalid value: 'custom'`. Only the responses API
|
|
5
|
+
* takes them, and only the reasoning models serve it, so the wire API follows
|
|
6
|
+
* the model rather than being a flag nobody would know to set. Older models are
|
|
7
|
+
* listed rather than newer ones, so each new generation works without a release.
|
|
8
|
+
*/
|
|
9
|
+
export declare function wireApi(provider: Provider, id: string): string | undefined;
|
|
10
|
+
export interface Wiring {
|
|
11
|
+
provider: Provider;
|
|
12
|
+
/** what the model is called on the wire */
|
|
13
|
+
model: string;
|
|
14
|
+
/** every `COPILOT_*` name and value, ready to hand to the child */
|
|
15
|
+
env: Record<string, string>;
|
|
16
|
+
/** names whose values are secret, for `--secret-env-vars` and for masking */
|
|
17
|
+
secret: string[];
|
|
18
|
+
/** anything true but unwelcome: a location that will be slow, a stale default */
|
|
19
|
+
warnings: string[];
|
|
20
|
+
/** Claude on Vertex: a loopback relay to start, whose url becomes the base url */
|
|
21
|
+
relay?: {
|
|
22
|
+
key: string;
|
|
23
|
+
project: string;
|
|
24
|
+
location: string;
|
|
25
|
+
keyFile: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** Where the relay's url goes once it is listening. */
|
|
29
|
+
export declare const RELAY_URL = "http://127.0.0.1:<relay>";
|
|
30
|
+
/**
|
|
31
|
+
* Turns a zen key entry and a model id into copilot's environment.
|
|
32
|
+
*
|
|
33
|
+
* `COPILOT_PROVIDER_BASE_URL` is what activates BYOK at all, so every provider
|
|
34
|
+
* gets one — including OpenAI, whose url copilot would otherwise have reached
|
|
35
|
+
* through a GitHub account we are deliberately not using.
|
|
36
|
+
*/
|
|
37
|
+
export declare function wire(store: KeyStore, entry: KeyEntry, id: string): Wiring;
|
|
38
|
+
/** Values a `--dry-run` may print. A key is four characters and an apology. */
|
|
39
|
+
export declare function masked(env: Record<string, string>, secret: readonly string[]): string[];
|
|
40
|
+
/** Non-fatal, said once, because a competing file is a silent override. */
|
|
41
|
+
export declare function providersWarning(): string | undefined;
|
|
42
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { credentialError, envOf, invokedAs, yellow, } from "./host.js";
|
|
4
|
+
const TYPES = {
|
|
5
|
+
openai: 'openai',
|
|
6
|
+
anthropic: 'anthropic',
|
|
7
|
+
google: 'openai',
|
|
8
|
+
vertex: 'openai',
|
|
9
|
+
openrouter: 'openai',
|
|
10
|
+
};
|
|
11
|
+
const BASE_URLS = {
|
|
12
|
+
openai: 'https://api.openai.com/v1',
|
|
13
|
+
anthropic: 'https://api.anthropic.com',
|
|
14
|
+
google: 'https://generativelanguage.googleapis.com/v1beta/openai/',
|
|
15
|
+
openrouter: 'https://openrouter.ai/api/v1',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Copilot offers its tools as OpenAI *custom* tools, which the completions API
|
|
19
|
+
* rejects outright — `400 Invalid value: 'custom'`. Only the responses API
|
|
20
|
+
* takes them, and only the reasoning models serve it, so the wire API follows
|
|
21
|
+
* the model rather than being a flag nobody would know to set. Older models are
|
|
22
|
+
* listed rather than newer ones, so each new generation works without a release.
|
|
23
|
+
*/
|
|
24
|
+
export function wireApi(provider, id) {
|
|
25
|
+
return provider === 'openai' && !/^(gpt-4|gpt-3\.5|chatgpt-)/.test(id)
|
|
26
|
+
? 'responses'
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Where the relay's url goes once it is listening. */
|
|
30
|
+
export const RELAY_URL = 'http://127.0.0.1:<relay>';
|
|
31
|
+
const vertexClaude = (id) => /^(anthropic\/)?claude-/.test(id);
|
|
32
|
+
/**
|
|
33
|
+
* Turns a zen key entry and a model id into copilot's environment.
|
|
34
|
+
*
|
|
35
|
+
* `COPILOT_PROVIDER_BASE_URL` is what activates BYOK at all, so every provider
|
|
36
|
+
* gets one — including OpenAI, whose url copilot would otherwise have reached
|
|
37
|
+
* through a GitHub account we are deliberately not using.
|
|
38
|
+
*/
|
|
39
|
+
export function wire(store, entry, id) {
|
|
40
|
+
const provider = entry.provider;
|
|
41
|
+
const env = {};
|
|
42
|
+
const secret = [];
|
|
43
|
+
const warnings = [];
|
|
44
|
+
env.COPILOT_PROVIDER_TYPE = TYPES[provider];
|
|
45
|
+
env.COPILOT_MODEL = id;
|
|
46
|
+
if (provider === 'vertex' && vertexClaude(id)) {
|
|
47
|
+
const project = store.projectOf(entry)?.id;
|
|
48
|
+
if (entry.holds !== 'file' || !project) {
|
|
49
|
+
throw credentialError(`Claude on vertex needs a service account and a GCP project, not ${entry.provider}/${entry.name}`, 'add one: zen key add vertex --project <id>');
|
|
50
|
+
}
|
|
51
|
+
const model = id.replace(/^anthropic\//, '');
|
|
52
|
+
const key = randomBytes(24).toString('hex');
|
|
53
|
+
env.COPILOT_PROVIDER_TYPE = 'anthropic';
|
|
54
|
+
env.COPILOT_MODEL = model;
|
|
55
|
+
env.COPILOT_PROVIDER_BASE_URL = RELAY_URL;
|
|
56
|
+
env.COPILOT_PROVIDER_API_KEY = key;
|
|
57
|
+
return {
|
|
58
|
+
provider,
|
|
59
|
+
model,
|
|
60
|
+
env,
|
|
61
|
+
secret: ['COPILOT_PROVIDER_API_KEY'],
|
|
62
|
+
warnings,
|
|
63
|
+
relay: {
|
|
64
|
+
key,
|
|
65
|
+
project,
|
|
66
|
+
location: entry.location?.trim() || 'global',
|
|
67
|
+
keyFile: store.fileOf(entry),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (provider === 'vertex') {
|
|
72
|
+
const project = store.projectOf(entry)?.id;
|
|
73
|
+
if (!project) {
|
|
74
|
+
throw credentialError(`no GCP project on ${entry.provider}/${entry.name}`, 'set one: zen key add vertex --project <id>');
|
|
75
|
+
}
|
|
76
|
+
const named = entry.location?.trim() || 'global';
|
|
77
|
+
const partner = id.includes('/') && !id.startsWith('google/');
|
|
78
|
+
// Gemini serves the `us`/`eu` multi-regions; partner models answer them with a 404 or 429.
|
|
79
|
+
const location = partner && (named === 'us' || named === 'eu') ? 'global' : named;
|
|
80
|
+
if (location === 'global' && !partner) {
|
|
81
|
+
warnings.push('location `global` adds about ten seconds of cold start');
|
|
82
|
+
}
|
|
83
|
+
env.COPILOT_PROVIDER_BASE_URL = `https://aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/endpoints/openapi`;
|
|
84
|
+
// Vertex speaks publisher names on the wire and knows nothing of zen's
|
|
85
|
+
// provider prefix; the bare id stays as the model *id* so copilot can
|
|
86
|
+
// still match its catalogue for token limits and tool support.
|
|
87
|
+
env.COPILOT_PROVIDER_WIRE_MODEL = id.includes('/') ? id : `google/${id}`;
|
|
88
|
+
// Only Google's own ids are bare: `gpt-oss-…` would get GPT's custom `apply_patch`, which this endpoint rejects.
|
|
89
|
+
env.COPILOT_PROVIDER_MODEL_ID = id.startsWith('google/') ? id.slice('google/'.length) : id;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const url = BASE_URLS[provider];
|
|
93
|
+
if (!url) {
|
|
94
|
+
throw credentialError(`no BYOK endpoint known for ${provider}`);
|
|
95
|
+
}
|
|
96
|
+
env.COPILOT_PROVIDER_BASE_URL = url;
|
|
97
|
+
}
|
|
98
|
+
const api = wireApi(provider, id);
|
|
99
|
+
if (api) {
|
|
100
|
+
env.COPILOT_PROVIDER_WIRE_API = api;
|
|
101
|
+
}
|
|
102
|
+
if (entry.holds === 'file') {
|
|
103
|
+
// A Google access token is good for an hour and a session is not, so
|
|
104
|
+
// copilot is told how to mint one rather than handed one that will be
|
|
105
|
+
// stale by the time it matters.
|
|
106
|
+
env.COPILOT_PROVIDER_API_KEY_COMMAND = `${invokedAs('zen')} key token ${entry.provider}`;
|
|
107
|
+
env[envOf(entry)] = store.fileOf(entry);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
env.COPILOT_PROVIDER_API_KEY = store.reveal(entry);
|
|
111
|
+
secret.push('COPILOT_PROVIDER_API_KEY');
|
|
112
|
+
}
|
|
113
|
+
return { provider, model: env.COPILOT_PROVIDER_WIRE_MODEL ?? id, env, secret, warnings };
|
|
114
|
+
}
|
|
115
|
+
/** Values a `--dry-run` may print. A key is four characters and an apology. */
|
|
116
|
+
export function masked(env, secret) {
|
|
117
|
+
return Object.entries(env)
|
|
118
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
119
|
+
.map(([name, value]) => `${name}=${secret.includes(name) ? '••••' : value}`);
|
|
120
|
+
}
|
|
121
|
+
/** Non-fatal, said once, because a competing file is a silent override. */
|
|
122
|
+
export function providersWarning() {
|
|
123
|
+
const configured = process.env.COPILOT_PROVIDERS_CONFIG;
|
|
124
|
+
const path = configured ?? `${process.env.HOME ?? ''}/.copilot/providers.json`;
|
|
125
|
+
return existsSync(path)
|
|
126
|
+
? yellow(`${path} may override the provider zen just wired`)
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=wire.js.map
|
package/dist/meta.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ModelUsage, TokenUsage } from '@zenera/neo';
|
|
1
2
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
3
|
import { type KeyEntry, type KeyStore, type Provider } from './keys.ts';
|
|
3
4
|
export declare const PROMPT_DIR = ".github/prompts";
|
|
@@ -60,7 +61,8 @@ export declare function misspelledProvider(ref: string): Provider | undefined;
|
|
|
60
61
|
* Copilot offers its tools as OpenAI *custom* tools, which the completions API
|
|
61
62
|
* rejects outright — `400 Invalid value: 'custom'`. Only the responses API
|
|
62
63
|
* takes them, and only the reasoning models serve it, so the wire API follows
|
|
63
|
-
* the model rather than being a flag nobody would know to set.
|
|
64
|
+
* the model rather than being a flag nobody would know to set. Older models are
|
|
65
|
+
* listed rather than newer ones, so each new generation works without a release.
|
|
64
66
|
*/
|
|
65
67
|
export declare function wireApi(provider: Provider, id: string): string | undefined;
|
|
66
68
|
export interface Wiring {
|
|
@@ -195,6 +197,18 @@ export interface Log {
|
|
|
195
197
|
}
|
|
196
198
|
/** `<project>/.tmp/logs/meta.<when>.log`, opened for append. */
|
|
197
199
|
export declare function openLog(dir: string, now?: Date): Log;
|
|
200
|
+
/**
|
|
201
|
+
* Every meta session the project ran and how it ended, so `zen meta resume`
|
|
202
|
+
* needs no id. `running` is never overwritten by a run that was killed, so it
|
|
203
|
+
* reads the same as `failed`: not finished.
|
|
204
|
+
*/
|
|
205
|
+
export type SessionState = 'running' | 'done' | 'failed';
|
|
206
|
+
export declare function recordSession(dir: string, id: string, state: SessionState): void;
|
|
207
|
+
/**
|
|
208
|
+
* The newest session that did not finish, else the newest of all. A quick
|
|
209
|
+
* question asked after a long run stopped must not stand in for it.
|
|
210
|
+
*/
|
|
211
|
+
export declare function lastSession(dir: string): string | undefined;
|
|
198
212
|
/**
|
|
199
213
|
* Folds one event into the outcome and says what, if anything, to show for it.
|
|
200
214
|
*
|
|
@@ -211,10 +225,66 @@ export interface Launch {
|
|
|
211
225
|
sink?: Sink;
|
|
212
226
|
log?: Log;
|
|
213
227
|
width?: number;
|
|
228
|
+
/** said after the elapsed time on the status row, e.g. the tokens so far */
|
|
229
|
+
status?: () => string;
|
|
214
230
|
/** injected by the tests, which have no copilot and want none */
|
|
215
231
|
spawn?: typeof nodeSpawn;
|
|
216
232
|
}
|
|
217
233
|
export declare function launch(opts: Launch): Promise<Outcome>;
|
|
234
|
+
/** Resumes after copilot's own retries run out; `--retries` overrides. */
|
|
235
|
+
export declare const DEFAULT_RESUMES = 5;
|
|
236
|
+
/** What the resumed session is told, since `-p` has to say something. */
|
|
237
|
+
export declare const RESUME_PROMPT = "The previous model call failed. Continue the task from where you stopped.";
|
|
238
|
+
export declare function resumeDelayMs(attempt: number): number;
|
|
239
|
+
/**
|
|
240
|
+
* Why the run is worth resuming, or undefined when waiting cannot help.
|
|
241
|
+
*
|
|
242
|
+
* Only a refusal that time can cure counts: a rate limit, the provider's own
|
|
243
|
+
* failure, a connection that timed out. A 400 or a bad key fails the same way
|
|
244
|
+
* however often it is sent - except the one below.
|
|
245
|
+
*/
|
|
246
|
+
export declare function transient(out: Outcome): string | undefined;
|
|
247
|
+
export interface MetaCall {
|
|
248
|
+
model: string;
|
|
249
|
+
usage: TokenUsage;
|
|
250
|
+
cacheWriteTokens?: number;
|
|
251
|
+
startedAt: string;
|
|
252
|
+
durationMs: number;
|
|
253
|
+
session?: string;
|
|
254
|
+
traceId?: string;
|
|
255
|
+
spanId?: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* One line of the span file, as a model call - anything else is undefined.
|
|
259
|
+
* Only counts are read off it: whatever else a span may carry, none of it
|
|
260
|
+
* reaches the ledger.
|
|
261
|
+
*/
|
|
262
|
+
export declare function readSpan(line: string): MetaCall | undefined;
|
|
263
|
+
/** Whole lines out of a stream read in chunks; the unfinished tail is carried. */
|
|
264
|
+
export declare function splitLines(carry: string, chunk: string): {
|
|
265
|
+
lines: string[];
|
|
266
|
+
carry: string;
|
|
267
|
+
};
|
|
268
|
+
/** Reads a span file as it grows. `stop()` reads what is left, once. */
|
|
269
|
+
export declare function tailSpans(path: string, onCall: (call: MetaCall) => void, everyMs?: number): {
|
|
270
|
+
stop: () => void;
|
|
271
|
+
};
|
|
272
|
+
/** Calls and tokens so far, in the one line the status row and the summary share. */
|
|
273
|
+
export declare class Tally {
|
|
274
|
+
calls: number;
|
|
275
|
+
usage: TokenUsage;
|
|
276
|
+
add(call: MetaCall): void;
|
|
277
|
+
toString(): string;
|
|
278
|
+
}
|
|
279
|
+
/** 950, 12.3k, 1.8M. */
|
|
280
|
+
export declare function compact(n: number): string;
|
|
281
|
+
/**
|
|
282
|
+
* Copilot's own per-model totals from the last `session.shutdown` in a session's
|
|
283
|
+
* events - cumulative over every resume of it. Reasoning folded into output,
|
|
284
|
+
* as everywhere in zen.
|
|
285
|
+
*/
|
|
286
|
+
export declare function shutdownModels(events: string): ModelUsage[] | undefined;
|
|
287
|
+
export declare function sessionTotals(sessionId: string): ModelUsage[] | undefined;
|
|
218
288
|
/** Non-fatal, said once, because a competing file is a silent override. */
|
|
219
289
|
export declare function providersWarning(): string | undefined;
|
|
220
290
|
export {};
|
package/dist/meta.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
-
import { createWriteStream, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { appendFileSync, closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync, } from 'node:fs';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
4
6
|
import { createInterface } from 'node:readline';
|
|
5
7
|
import { invokedAs } from "./args.js";
|
|
6
8
|
import { paths, readJson, writeJson } from "./home.js";
|
|
7
9
|
import { envOf } from "./keys.js";
|
|
10
|
+
import { duration } from "./narrate.js";
|
|
8
11
|
import { credentialError, cut, cyan, dim, note, pad, plain, red, styled, usageError, write, yellow, } from "./term.js";
|
|
9
12
|
import { formatInline, formatMarkdown } from "./tui/markdown.js";
|
|
10
13
|
import { boxWidth } from "./tui/wrap.js";
|
|
@@ -174,10 +177,13 @@ const BASE_URLS = {
|
|
|
174
177
|
* Copilot offers its tools as OpenAI *custom* tools, which the completions API
|
|
175
178
|
* rejects outright — `400 Invalid value: 'custom'`. Only the responses API
|
|
176
179
|
* takes them, and only the reasoning models serve it, so the wire API follows
|
|
177
|
-
* the model rather than being a flag nobody would know to set.
|
|
180
|
+
* the model rather than being a flag nobody would know to set. Older models are
|
|
181
|
+
* listed rather than newer ones, so each new generation works without a release.
|
|
178
182
|
*/
|
|
179
183
|
export function wireApi(provider, id) {
|
|
180
|
-
return provider === 'openai' &&
|
|
184
|
+
return provider === 'openai' && !/^(gpt-4|gpt-3\.5|chatgpt-)/.test(id)
|
|
185
|
+
? 'responses'
|
|
186
|
+
: undefined;
|
|
181
187
|
}
|
|
182
188
|
/**
|
|
183
189
|
* Turns a zen key entry and a model id into copilot's environment.
|
|
@@ -529,6 +535,41 @@ export function openLog(dir, now = new Date()) {
|
|
|
529
535
|
},
|
|
530
536
|
};
|
|
531
537
|
}
|
|
538
|
+
const sessionLog = (dir) => `${dir}/.tmp/logs/meta.sessions`;
|
|
539
|
+
/** The single-id file older versions wrote. */
|
|
540
|
+
const legacySession = (dir) => `${dir}/.tmp/logs/meta.session`;
|
|
541
|
+
export function recordSession(dir, id, state) {
|
|
542
|
+
mkdirSync(`${dir}/.tmp/logs`, { recursive: true });
|
|
543
|
+
appendFileSync(sessionLog(dir), `${new Date().toISOString()}\t${id}\t${state}\n`);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* The newest session that did not finish, else the newest of all. A quick
|
|
547
|
+
* question asked after a long run stopped must not stand in for it.
|
|
548
|
+
*/
|
|
549
|
+
export function lastSession(dir) {
|
|
550
|
+
let text;
|
|
551
|
+
try {
|
|
552
|
+
text = readFileSync(sessionLog(dir), 'utf8');
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
try {
|
|
556
|
+
return readFileSync(legacySession(dir), 'utf8').trim() || undefined;
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
return undefined;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const latest = new Map();
|
|
563
|
+
for (const line of text.split('\n')) {
|
|
564
|
+
const [, id, state] = line.split('\t');
|
|
565
|
+
if (id && state) {
|
|
566
|
+
latest.delete(id);
|
|
567
|
+
latest.set(id, state);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const newestFirst = [...latest].reverse();
|
|
571
|
+
return (newestFirst.find(([, state]) => state !== 'done') ?? newestFirst[0])?.[0];
|
|
572
|
+
}
|
|
532
573
|
/** Everything the sink is told, kept by the log too — and the detail only there. */
|
|
533
574
|
function tee(sink, log) {
|
|
534
575
|
return {
|
|
@@ -630,8 +671,8 @@ export async function launch(opts) {
|
|
|
630
671
|
// after the model line is indistinguishable from a hang.
|
|
631
672
|
const began = Date.now();
|
|
632
673
|
const beat = setInterval(() => {
|
|
633
|
-
const
|
|
634
|
-
sink.status?.(`working: ${
|
|
674
|
+
const extra = opts.status?.();
|
|
675
|
+
sink.status?.(`working: ${duration(Date.now() - began)}${extra ? ` · ${extra}` : ''}`);
|
|
635
676
|
}, FRAME_MS);
|
|
636
677
|
beat.unref();
|
|
637
678
|
child.stderr?.on('data', (chunk) => {
|
|
@@ -658,7 +699,7 @@ export async function launch(opts) {
|
|
|
658
699
|
absorb(event, out, sink, width);
|
|
659
700
|
}
|
|
660
701
|
const code = await new Promise((resolve) => {
|
|
661
|
-
child.on('close', (value) => resolve(value ?? 0));
|
|
702
|
+
child.on('close', (value, signal) => resolve(value ?? (signal ? 1 : 0)));
|
|
662
703
|
});
|
|
663
704
|
if (out.exitCode === 0 && code !== 0) {
|
|
664
705
|
out.exitCode = code;
|
|
@@ -673,6 +714,262 @@ export async function launch(opts) {
|
|
|
673
714
|
}
|
|
674
715
|
return out;
|
|
675
716
|
}
|
|
717
|
+
// ---------------------------------------------------------------------------
|
|
718
|
+
// Resuming
|
|
719
|
+
//
|
|
720
|
+
// Copilot retries a failed model call itself, five times over about a minute,
|
|
721
|
+
// and has no setting to change that. A run that outlasts it is still a session
|
|
722
|
+
// on disk, so the retry happens one level up: wait, then resume that session.
|
|
723
|
+
// ---------------------------------------------------------------------------
|
|
724
|
+
/** Resumes after copilot's own retries run out; `--retries` overrides. */
|
|
725
|
+
export const DEFAULT_RESUMES = 5;
|
|
726
|
+
/** The first wait; it doubles per resume, up to `RESUME_MAX_MS`. */
|
|
727
|
+
const RESUME_INITIAL_MS = 30_000;
|
|
728
|
+
const RESUME_MAX_MS = 300_000;
|
|
729
|
+
/** What the resumed session is told, since `-p` has to say something. */
|
|
730
|
+
export const RESUME_PROMPT = 'The previous model call failed. Continue the task from where you stopped.';
|
|
731
|
+
export function resumeDelayMs(attempt) {
|
|
732
|
+
return Math.min(RESUME_INITIAL_MS * 2 ** attempt, RESUME_MAX_MS);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Why the run is worth resuming, or undefined when waiting cannot help.
|
|
736
|
+
*
|
|
737
|
+
* Only a refusal that time can cure counts: a rate limit, the provider's own
|
|
738
|
+
* failure, a connection that timed out. A 400 or a bad key fails the same way
|
|
739
|
+
* however often it is sent - except the one below.
|
|
740
|
+
*/
|
|
741
|
+
export function transient(out) {
|
|
742
|
+
if (out.exitCode === 0 || !out.sessionId) {
|
|
743
|
+
return undefined;
|
|
744
|
+
}
|
|
745
|
+
const at = out.events.findLastIndex((e) => e.type === 'session.error');
|
|
746
|
+
const data = out.events[at]?.data;
|
|
747
|
+
if (!data) {
|
|
748
|
+
return undefined;
|
|
749
|
+
}
|
|
750
|
+
const status = Number(data.statusCode ?? 0);
|
|
751
|
+
if (data.errorType === 'rate_limit' || status === 429) {
|
|
752
|
+
return 'rate-limited (429)';
|
|
753
|
+
}
|
|
754
|
+
if (status === 408 || status >= 500) {
|
|
755
|
+
return `the provider failed (${status})`;
|
|
756
|
+
}
|
|
757
|
+
if (/timed out|connection error|ECONNRESET|ETIMEDOUT|EAI_AGAIN|socket hang up/i.test(String(data.message ?? ''))) {
|
|
758
|
+
return 'the connection failed';
|
|
759
|
+
}
|
|
760
|
+
if (status === 400 && injected(out.events.slice(0, at))) {
|
|
761
|
+
return 'refused a tool call copilot made itself (400)';
|
|
762
|
+
}
|
|
763
|
+
return undefined;
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Whether the turn that failed began with a tool call copilot wrote, not the
|
|
767
|
+
* model. When a background command finishes, copilot adds a `read_bash` of its
|
|
768
|
+
* own; Gemini 3 rejects any tool call in the current turn that lacks its thought
|
|
769
|
+
* signature, so that request is a 400 every time. A new user message - the
|
|
770
|
+
* resume - starts a turn that no longer holds it.
|
|
771
|
+
*/
|
|
772
|
+
function injected(before) {
|
|
773
|
+
for (let i = before.length - 1; i >= 0; i--) {
|
|
774
|
+
const event = before[i];
|
|
775
|
+
if (event.type === 'system.notification') {
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
if (event.type === 'user.message') {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
if (event.type === 'assistant.message') {
|
|
782
|
+
if (event.data?.model) {
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
const requests = event.data?.toolRequests;
|
|
786
|
+
if (Array.isArray(requests) && requests.length > 0) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
/** An OTel time: `[seconds, nanoseconds]` in this exporter; epoch ms either way. */
|
|
794
|
+
function epochMs(t) {
|
|
795
|
+
if (Array.isArray(t) && typeof t[0] === 'number') {
|
|
796
|
+
return t[0] * 1000 + Number(t[1] ?? 0) / 1e6;
|
|
797
|
+
}
|
|
798
|
+
if (typeof t === 'number') {
|
|
799
|
+
return t;
|
|
800
|
+
}
|
|
801
|
+
return typeof t === 'string' ? Date.parse(t) : undefined;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* One line of the span file, as a model call - anything else is undefined.
|
|
805
|
+
* Only counts are read off it: whatever else a span may carry, none of it
|
|
806
|
+
* reaches the ledger.
|
|
807
|
+
*/
|
|
808
|
+
export function readSpan(line) {
|
|
809
|
+
let span;
|
|
810
|
+
try {
|
|
811
|
+
span = JSON.parse(line);
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
return undefined;
|
|
815
|
+
}
|
|
816
|
+
const attrs = (span.attributes ?? {});
|
|
817
|
+
if (span.type !== 'span' || attrs['gen_ai.operation.name'] !== 'chat') {
|
|
818
|
+
return undefined;
|
|
819
|
+
}
|
|
820
|
+
const num = (key) => Number(attrs[key] ?? 0) || 0;
|
|
821
|
+
const start = epochMs(span.startTime) ?? 0;
|
|
822
|
+
const end = epochMs(span.endTime) ?? start;
|
|
823
|
+
// Copilot counts reasoning beside the output; zen counts it inside.
|
|
824
|
+
const reasoning = num('gen_ai.usage.reasoning.output_tokens');
|
|
825
|
+
const cacheWrite = num('gen_ai.usage.cache_creation.input_tokens') ||
|
|
826
|
+
num('gen_ai.usage.cache_creation_input_tokens');
|
|
827
|
+
return {
|
|
828
|
+
model: String(attrs['gen_ai.response.model'] ?? attrs['gen_ai.request.model'] ?? '?'),
|
|
829
|
+
usage: {
|
|
830
|
+
inputTokens: num('gen_ai.usage.input_tokens'),
|
|
831
|
+
cachedInputTokens: num('gen_ai.usage.cache_read.input_tokens') ||
|
|
832
|
+
num('gen_ai.usage.cache_read_input_tokens'),
|
|
833
|
+
outputTokens: num('gen_ai.usage.output_tokens') + reasoning,
|
|
834
|
+
reasoningTokens: reasoning,
|
|
835
|
+
},
|
|
836
|
+
...(cacheWrite ? { cacheWriteTokens: cacheWrite } : {}),
|
|
837
|
+
startedAt: new Date(start).toISOString(),
|
|
838
|
+
durationMs: Math.max(0, Math.round(end - start)),
|
|
839
|
+
session: typeof attrs['gen_ai.conversation.id'] === 'string'
|
|
840
|
+
? attrs['gen_ai.conversation.id']
|
|
841
|
+
: undefined,
|
|
842
|
+
traceId: typeof span.traceId === 'string' ? span.traceId : undefined,
|
|
843
|
+
spanId: typeof span.spanId === 'string' ? span.spanId : undefined,
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/** Whole lines out of a stream read in chunks; the unfinished tail is carried. */
|
|
847
|
+
export function splitLines(carry, chunk) {
|
|
848
|
+
const parts = `${carry}${chunk}`.split('\n');
|
|
849
|
+
const rest = parts.pop() ?? '';
|
|
850
|
+
return { lines: parts.filter((l) => l.trim()), carry: rest };
|
|
851
|
+
}
|
|
852
|
+
/** Reads a span file as it grows. `stop()` reads what is left, once. */
|
|
853
|
+
export function tailSpans(path, onCall, everyMs = 1000) {
|
|
854
|
+
let offset = 0;
|
|
855
|
+
let carry = '';
|
|
856
|
+
const poll = () => {
|
|
857
|
+
let fd;
|
|
858
|
+
try {
|
|
859
|
+
const size = statSync(path).size;
|
|
860
|
+
if (size <= offset) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
fd = openSync(path, 'r');
|
|
864
|
+
const buf = Buffer.alloc(size - offset);
|
|
865
|
+
readSync(fd, buf, 0, buf.length, offset);
|
|
866
|
+
offset = size;
|
|
867
|
+
const split = splitLines(carry, buf.toString('utf8'));
|
|
868
|
+
carry = split.carry;
|
|
869
|
+
for (const line of split.lines) {
|
|
870
|
+
const call = readSpan(line);
|
|
871
|
+
if (call) {
|
|
872
|
+
onCall(call);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
// Not there yet: copilot creates it at the first span.
|
|
878
|
+
}
|
|
879
|
+
finally {
|
|
880
|
+
if (fd !== undefined) {
|
|
881
|
+
closeSync(fd);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
const timer = setInterval(poll, everyMs);
|
|
886
|
+
timer.unref();
|
|
887
|
+
return {
|
|
888
|
+
stop: () => {
|
|
889
|
+
clearInterval(timer);
|
|
890
|
+
poll();
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
/** Calls and tokens so far, in the one line the status row and the summary share. */
|
|
895
|
+
export class Tally {
|
|
896
|
+
calls = 0;
|
|
897
|
+
usage = {
|
|
898
|
+
inputTokens: 0,
|
|
899
|
+
cachedInputTokens: 0,
|
|
900
|
+
outputTokens: 0,
|
|
901
|
+
reasoningTokens: 0,
|
|
902
|
+
};
|
|
903
|
+
add(call) {
|
|
904
|
+
this.calls += 1;
|
|
905
|
+
this.usage = {
|
|
906
|
+
inputTokens: this.usage.inputTokens + call.usage.inputTokens,
|
|
907
|
+
cachedInputTokens: this.usage.cachedInputTokens + call.usage.cachedInputTokens,
|
|
908
|
+
outputTokens: this.usage.outputTokens + call.usage.outputTokens,
|
|
909
|
+
reasoningTokens: this.usage.reasoningTokens + call.usage.reasoningTokens,
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
toString() {
|
|
913
|
+
const u = this.usage;
|
|
914
|
+
const cached = u.cachedInputTokens ? ` (${compact(u.cachedInputTokens)} cached)` : '';
|
|
915
|
+
return (`${this.calls} call${this.calls === 1 ? '' : 's'} · ${compact(u.inputTokens)} in${cached}` +
|
|
916
|
+
` · ${compact(u.outputTokens)} out`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
/** 950, 12.3k, 1.8M. */
|
|
920
|
+
export function compact(n) {
|
|
921
|
+
if (n < 1000) {
|
|
922
|
+
return String(n);
|
|
923
|
+
}
|
|
924
|
+
if (n < 1e6) {
|
|
925
|
+
return `${(n / 1e3).toFixed(n < 1e4 ? 1 : 0)}k`;
|
|
926
|
+
}
|
|
927
|
+
return `${(n / 1e6).toFixed(n < 1e7 ? 2 : 1)}M`;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Copilot's own per-model totals from the last `session.shutdown` in a session's
|
|
931
|
+
* events - cumulative over every resume of it. Reasoning folded into output,
|
|
932
|
+
* as everywhere in zen.
|
|
933
|
+
*/
|
|
934
|
+
export function shutdownModels(events) {
|
|
935
|
+
const line = events
|
|
936
|
+
.split('\n')
|
|
937
|
+
.reverse()
|
|
938
|
+
.find((l) => l.includes('"session.shutdown"'));
|
|
939
|
+
if (!line) {
|
|
940
|
+
return undefined;
|
|
941
|
+
}
|
|
942
|
+
let metrics;
|
|
943
|
+
try {
|
|
944
|
+
metrics = JSON.parse(line).data?.modelMetrics ?? {};
|
|
945
|
+
}
|
|
946
|
+
catch {
|
|
947
|
+
return undefined;
|
|
948
|
+
}
|
|
949
|
+
return Object.entries(metrics).map(([model, m]) => {
|
|
950
|
+
const u = m.usage ?? {};
|
|
951
|
+
const reasoning = Number(u.reasoningTokens ?? 0);
|
|
952
|
+
return {
|
|
953
|
+
model,
|
|
954
|
+
calls: Number(m.requests?.count ?? 0),
|
|
955
|
+
usage: {
|
|
956
|
+
inputTokens: Number(u.inputTokens ?? 0),
|
|
957
|
+
cachedInputTokens: Number(u.cacheReadTokens ?? 0),
|
|
958
|
+
outputTokens: Number(u.outputTokens ?? 0) + reasoning,
|
|
959
|
+
reasoningTokens: reasoning,
|
|
960
|
+
},
|
|
961
|
+
};
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
export function sessionTotals(sessionId) {
|
|
965
|
+
const home = process.env.COPILOT_HOME ?? join(homedir(), '.copilot');
|
|
966
|
+
try {
|
|
967
|
+
return shutdownModels(readFileSync(join(home, 'session-state', sessionId, 'events.jsonl'), 'utf8'));
|
|
968
|
+
}
|
|
969
|
+
catch {
|
|
970
|
+
return undefined;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
676
973
|
/** Non-fatal, said once, because a competing file is a silent override. */
|
|
677
974
|
export function providersWarning() {
|
|
678
975
|
const configured = process.env.COPILOT_PROVIDERS_CONFIG;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Provider } from './keys.ts';
|
|
2
|
+
export declare const PROVIDER_NAMES: ReadonlySet<string>;
|
|
3
|
+
/**
|
|
4
|
+
* `vertex/gemini-3.8-flash` splits; `gpt-5.6-sol` does not. Only a known provider
|
|
5
|
+
* counts as a prefix, because `google/gemini-3.8-flash` is also a perfectly good
|
|
6
|
+
* bare id on an endpoint that speaks publisher names.
|
|
7
|
+
*
|
|
8
|
+
* The colon form is the library's own — `openai:gpt-5.6-sol`, and with an api
|
|
9
|
+
* selector `openai/responses:gpt-5.6-sol` — which is what arrives when the ref
|
|
10
|
+
* came from `agents.yaml`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function splitRef(ref: string): {
|
|
13
|
+
provider?: Provider;
|
|
14
|
+
id: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=modelref.d.ts.map
|
package/dist/modelref.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Model refs as people type them
|
|
3
|
+
//
|
|
4
|
+
// `vertex/gemini-3.8-flash` from a person, `openai:gpt-5.6-sol` from
|
|
5
|
+
// `agents.yaml`. Read by `zen inspect ask` and by `zen meta`, so it lives with
|
|
6
|
+
// neither.
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export const PROVIDER_NAMES = new Set([
|
|
9
|
+
'openai',
|
|
10
|
+
'anthropic',
|
|
11
|
+
'google',
|
|
12
|
+
'vertex',
|
|
13
|
+
'openrouter',
|
|
14
|
+
]);
|
|
15
|
+
/**
|
|
16
|
+
* `vertex/gemini-3.8-flash` splits; `gpt-5.6-sol` does not. Only a known provider
|
|
17
|
+
* counts as a prefix, because `google/gemini-3.8-flash` is also a perfectly good
|
|
18
|
+
* bare id on an endpoint that speaks publisher names.
|
|
19
|
+
*
|
|
20
|
+
* The colon form is the library's own — `openai:gpt-5.6-sol`, and with an api
|
|
21
|
+
* selector `openai/responses:gpt-5.6-sol` — which is what arrives when the ref
|
|
22
|
+
* came from `agents.yaml`.
|
|
23
|
+
*/
|
|
24
|
+
export function splitRef(ref) {
|
|
25
|
+
const colon = ref.indexOf(':');
|
|
26
|
+
if (colon > 0) {
|
|
27
|
+
const head = ref.slice(0, colon).split('/')[0];
|
|
28
|
+
if (PROVIDER_NAMES.has(head)) {
|
|
29
|
+
return { provider: head, id: ref.slice(colon + 1) };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const slash = ref.indexOf('/');
|
|
33
|
+
if (slash < 0) {
|
|
34
|
+
return { id: ref };
|
|
35
|
+
}
|
|
36
|
+
const head = ref.slice(0, slash);
|
|
37
|
+
return PROVIDER_NAMES.has(head)
|
|
38
|
+
? { provider: head, id: ref.slice(slash + 1) }
|
|
39
|
+
: { id: ref };
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=modelref.js.map
|