zames_pro 2.72.8 → 2.73.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.
@@ -0,0 +1,73 @@
1
+ // Provider-switch HANDOFF: when the operator changes `/provider` in the middle
2
+ // of a session, the current chat does NOT exist on the new backend (a chat id
3
+ // is meaningful only to the provider that created it), so the conversation
4
+ // would be lost. Instead we summarize the current chat on the OLD provider and
5
+ // persist a HANDOFF for the target provider; on the next launch the new
6
+ // provider opens a fresh chat and posts the summary as its first message.
7
+ //
8
+ // This mirrors /compact's carryover, but the summary crosses a provider
9
+ // boundary and survives a restart, so it is written to disk.
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import os from 'os';
13
+ import { writeJsonAtomic } from './fsutil.js';
14
+ const ZAMES_HOME = path.join(os.homedir(), '.zames');
15
+ const HANDOFF_FILE = path.join(ZAMES_HOME, 'provider-handoff.json');
16
+ const NL = String.fromCharCode(10);
17
+ /**
18
+ * Wrap a cross-provider summary into the first message of the new provider's
19
+ * chat. PURE (unit-tested): the wording must make clear this is carried-over
20
+ * context from ANOTHER assistant, so the model treats it as history, not as a
21
+ * fresh instruction to redo the work.
22
+ */
23
+ export function buildHandoffText(h) {
24
+ const body = String(h.summary ?? '').trim();
25
+ const goal = String(h.task ?? '').trim();
26
+ let out = `Context carried over from a previous session with another assistant ` +
27
+ `(${h.from} -> ${h.to}). Treat it as the history of the work so far and ` +
28
+ `continue from the current state. Do NOT redo what is already done.`;
29
+ out += NL + NL + body;
30
+ if (goal)
31
+ out += NL + NL + 'Original task: ' + goal;
32
+ return out;
33
+ }
34
+ /** Persist a handoff for a provider. Best-effort (never throws to the caller). */
35
+ export function saveProviderHandoff(h) {
36
+ try {
37
+ fs.mkdirSync(ZAMES_HOME, { recursive: true });
38
+ writeJsonAtomic(HANDOFF_FILE, h);
39
+ }
40
+ catch { }
41
+ }
42
+ /**
43
+ * Read the pending handoff, but ONLY when it targets `provider` and `workdir`:
44
+ * a handoff for a different provider or project must not be posted into the
45
+ * wrong chat. Returns null when there is none / it does not match.
46
+ */
47
+ export function readProviderHandoff(provider, workdir) {
48
+ try {
49
+ const raw = JSON.parse(fs.readFileSync(HANDOFF_FILE, 'utf-8'));
50
+ if (!raw || typeof raw.summary !== 'string' || !raw.summary.trim()) {
51
+ return null;
52
+ }
53
+ if (raw.provider !== provider)
54
+ return null;
55
+ if ((raw.workdir || '') !== (workdir || ''))
56
+ return null;
57
+ return raw;
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ /** Remove the handoff once it has been posted (or is stale). Best-effort. */
64
+ export function clearProviderHandoff() {
65
+ try {
66
+ fs.unlinkSync(HANDOFF_FILE);
67
+ }
68
+ catch { }
69
+ }
70
+ /** For tests: the file the handoff lives in. */
71
+ export function providerHandoffFile() {
72
+ return HANDOFF_FILE;
73
+ }
@@ -0,0 +1,50 @@
1
+ // Provider registry — the SINGLE source of truth for the chat backends zames
2
+ // can drive. Adding a new backend (ChatGPT, Gemini, …) means:
3
+ // 1. add an entry here (id, label, chat URL prefix, extra config section);
4
+ // 2. add the provider's `<name>-ui.ts` + `<name>-net-capture.ts` +
5
+ // `<name>-browser.ts` (implementing ChatProvider, see types.ts);
6
+ // 3. register the class in `createBrowser()` in index.ts.
7
+ // Everything else (config enum, /provider validation, sessions tagging, the
8
+ // /doctor row, the {provider} label in operator messages, /chat URL) reads
9
+ // from HERE, so nothing else has to be touched.
10
+ //
11
+ // This module is PURE (no imports, no side effects) so it can be imported from
12
+ // anywhere without pulling in Playwright.
13
+ export const PROVIDERS = {
14
+ deepseek: {
15
+ id: 'deepseek',
16
+ label: 'DeepSeek',
17
+ chatUrlPrefix: 'https://chat.deepseek.com/a/chat/s',
18
+ isDefault: true,
19
+ },
20
+ qwen: {
21
+ id: 'qwen',
22
+ label: 'Qwen',
23
+ chatUrlPrefix: 'https://chat.qwen.ai/c',
24
+ isDefault: false,
25
+ },
26
+ };
27
+ /** All provider ids, in a stable order (default first). */
28
+ export const PROVIDER_IDS = Object.keys(PROVIDERS).sort((a, b) => PROVIDERS[a].isDefault === PROVIDERS[b].isDefault
29
+ ? a.localeCompare(b)
30
+ : PROVIDERS[a].isDefault
31
+ ? -1
32
+ : 1);
33
+ /** True when `v` is a known provider id. Narrows the type for callers. */
34
+ export function isProviderId(v) {
35
+ return (typeof v === 'string' && Object.prototype.hasOwnProperty.call(PROVIDERS, v));
36
+ }
37
+ /** Resolve an unknown value to a provider id, falling back to the default. */
38
+ export function asProviderId(v) {
39
+ if (isProviderId(v))
40
+ return v;
41
+ return PROVIDER_IDS.find((id) => PROVIDERS[id].isDefault) || PROVIDER_IDS[0];
42
+ }
43
+ /** The human label of a provider ('Qwen', 'DeepSeek'). */
44
+ export function providerLabel(id) {
45
+ return isProviderId(id) ? PROVIDERS[id].label : String(id);
46
+ }
47
+ /** The base chat URL of a provider, used by /chat. */
48
+ export function providerChatUrlPrefix(id) {
49
+ return isProviderId(id) ? PROVIDERS[id].chatUrlPrefix : '';
50
+ }