@yeaft/webchat-agent 0.1.412 → 0.1.414
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/connection/message-router.js +14 -0
- package/package.json +3 -2
- package/service/index.js +10 -0
- package/unify/cli.js +208 -106
- package/unify/config-api.js +117 -0
- package/unify/config.js +174 -181
- package/unify/engine.js +22 -4
- package/unify/index.js +2 -1
- package/unify/init.js +51 -54
- package/unify/llm/adapter.js +12 -0
- package/unify/llm/router.js +148 -0
- package/unify/models.js +46 -2
- package/unify/session.js +11 -6
- package/unify/stop-hooks.js +6 -1
package/unify/init.js
CHANGED
|
@@ -23,56 +23,28 @@ const SUBDIRS = [
|
|
|
23
23
|
'skills',
|
|
24
24
|
];
|
|
25
25
|
|
|
26
|
-
/** Default config.
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- \`claude-sonnet-4-20250514\` (default)
|
|
49
|
-
- \`claude-opus-4-20250514\`
|
|
50
|
-
- \`gpt-5\`, \`gpt-5.4\`, \`gpt-4.1\`, \`gpt-4.1-mini\`
|
|
51
|
-
- \`o3\`, \`o4-mini\`
|
|
52
|
-
- \`deepseek-chat\`, \`deepseek-reasoner\`
|
|
53
|
-
- \`gemini-2.5-pro\`, \`gemini-2.5-flash\`
|
|
54
|
-
|
|
55
|
-
## API Keys
|
|
56
|
-
|
|
57
|
-
Store API keys in \`~/.yeaft/.env\` (recommended) or export as env vars:
|
|
58
|
-
|
|
59
|
-
\`\`\`bash
|
|
60
|
-
# ~/.yeaft/.env
|
|
61
|
-
YEAFT_API_KEY=sk-ant-... # Anthropic
|
|
62
|
-
YEAFT_OPENAI_API_KEY=sk-... # OpenAI / DeepSeek / Gemini
|
|
63
|
-
\`\`\`
|
|
64
|
-
|
|
65
|
-
## Environment Variables
|
|
66
|
-
|
|
67
|
-
Shell env vars take precedence over .env and config.md:
|
|
68
|
-
|
|
69
|
-
- \`YEAFT_MODEL\` — override model ID
|
|
70
|
-
- \`YEAFT_LANGUAGE\` — language for system prompts (en/zh)
|
|
71
|
-
- \`YEAFT_API_KEY\` — Anthropic API key
|
|
72
|
-
- \`YEAFT_OPENAI_API_KEY\` — OpenAI-compatible API key
|
|
73
|
-
- \`YEAFT_PROXY_URL\` — CopilotProxy URL (default: http://localhost:6628)
|
|
74
|
-
- \`YEAFT_DEBUG\` — enable debug mode (1/true)
|
|
75
|
-
- \`YEAFT_DIR\` — data directory (default: ~/.yeaft)
|
|
26
|
+
/** Default config.json — generated on first init as default configuration. */
|
|
27
|
+
const DEFAULT_CONFIG_JSON = `{
|
|
28
|
+
"providers": [
|
|
29
|
+
{
|
|
30
|
+
"name": "my-proxy",
|
|
31
|
+
"baseUrl": "http://localhost:6628/v1",
|
|
32
|
+
"apiKey": "proxy",
|
|
33
|
+
"models": [
|
|
34
|
+
"claude-sonnet-4-20250514",
|
|
35
|
+
"claude-haiku-3-20250414",
|
|
36
|
+
"gpt-5",
|
|
37
|
+
"deepseek-chat"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"primaryModel": "my-proxy/claude-sonnet-4-20250514",
|
|
42
|
+
"fastModel": "my-proxy/claude-sonnet-4-20250514",
|
|
43
|
+
"language": "en",
|
|
44
|
+
"debug": false,
|
|
45
|
+
"maxContextTokens": 200000,
|
|
46
|
+
"messageTokenBudget": 8192
|
|
47
|
+
}
|
|
76
48
|
`;
|
|
77
49
|
|
|
78
50
|
/** Default MEMORY.md content. */
|
|
@@ -88,6 +60,23 @@ This file stores persistent memory entries. The agent will read and update this
|
|
|
88
60
|
|
|
89
61
|
`;
|
|
90
62
|
|
|
63
|
+
/** Default mcp.json example — generated as reference for MCP server configuration. */
|
|
64
|
+
const DEFAULT_MCP_EXAMPLE = `{
|
|
65
|
+
"_comment": "MCP server configuration. Rename to mcp.json to enable.",
|
|
66
|
+
"_docs": "Each server needs 'name' + 'command'. Optional: 'args', 'env'.",
|
|
67
|
+
"servers": [
|
|
68
|
+
{
|
|
69
|
+
"name": "example-github",
|
|
70
|
+
"command": "npx",
|
|
71
|
+
"args": ["@modelcontextprotocol/server-github"],
|
|
72
|
+
"env": {
|
|
73
|
+
"GITHUB_TOKEN": "ghp_your_token_here"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
91
80
|
/** Default conversation/index.md content. */
|
|
92
81
|
const DEFAULT_CONVERSATION_INDEX = `---
|
|
93
82
|
lastMessageId: null
|
|
@@ -125,10 +114,11 @@ export function initYeaftDir(dir) {
|
|
|
125
114
|
}
|
|
126
115
|
|
|
127
116
|
// Create default files if they don't exist
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
117
|
+
// config.json — default configuration (user edits this directly)
|
|
118
|
+
const configJsonPath = join(root, 'config.json');
|
|
119
|
+
if (!existsSync(configJsonPath)) {
|
|
120
|
+
writeFileSync(configJsonPath, DEFAULT_CONFIG_JSON, 'utf8');
|
|
121
|
+
created.push(configJsonPath);
|
|
132
122
|
}
|
|
133
123
|
|
|
134
124
|
const memoryPath = join(root, 'memory', 'MEMORY.md');
|
|
@@ -143,5 +133,12 @@ export function initYeaftDir(dir) {
|
|
|
143
133
|
created.push(indexPath);
|
|
144
134
|
}
|
|
145
135
|
|
|
136
|
+
// mcp.json.example — reference template for MCP server configuration
|
|
137
|
+
const mcpExamplePath = join(root, 'mcp.json.example');
|
|
138
|
+
if (!existsSync(mcpExamplePath)) {
|
|
139
|
+
writeFileSync(mcpExamplePath, DEFAULT_MCP_EXAMPLE, 'utf8');
|
|
140
|
+
created.push(mcpExamplePath);
|
|
141
|
+
}
|
|
142
|
+
|
|
146
143
|
return { dir: root, created };
|
|
147
144
|
}
|
package/unify/llm/adapter.js
CHANGED
|
@@ -143,10 +143,22 @@ export class LLMAdapter {
|
|
|
143
143
|
/**
|
|
144
144
|
* Create an LLM adapter based on configuration.
|
|
145
145
|
*
|
|
146
|
+
* If config.providers exists (config.json path), creates an AdapterRouter
|
|
147
|
+
* that routes requests to the correct provider based on model ID.
|
|
148
|
+
*
|
|
149
|
+
* Otherwise falls back to legacy single-adapter creation from env vars.
|
|
150
|
+
*
|
|
146
151
|
* @param {object} config — From loadConfig()
|
|
147
152
|
* @returns {Promise<LLMAdapter>}
|
|
148
153
|
*/
|
|
149
154
|
export async function createLLMAdapter(config) {
|
|
155
|
+
// ─── New path: config.json with providers ─────────────
|
|
156
|
+
if (config.providers && config.providers.length > 0) {
|
|
157
|
+
const { AdapterRouter } = await import('./router.js');
|
|
158
|
+
return new AdapterRouter({ providers: config.providers });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Legacy path: single adapter from env vars ────────
|
|
150
162
|
// Normalize adapter name — accept 'chat-completions' as alias for 'openai'
|
|
151
163
|
const adapter = config.adapter === 'chat-completions' ? 'openai' : config.adapter;
|
|
152
164
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* router.js — AdapterRouter: routes LLM calls to the correct provider adapter
|
|
3
|
+
*
|
|
4
|
+
* Given a providers array from config.json:
|
|
5
|
+
* [{ name, baseUrl, apiKey, protocol?, models[] }, ...]
|
|
6
|
+
*
|
|
7
|
+
* The router resolves model → provider, lazy-creates the right adapter
|
|
8
|
+
* (AnthropicAdapter or ChatCompletionsAdapter based on protocol), caches it,
|
|
9
|
+
* and forwards stream()/call() to the resolved adapter.
|
|
10
|
+
*
|
|
11
|
+
* protocol defaults to "openai" (Chat Completions API).
|
|
12
|
+
* Set protocol: "anthropic" only for direct Anthropic API connections.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { LLMAdapter } from './adapter.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* AdapterRouter — Implements LLMAdapter, routes by model → provider.
|
|
19
|
+
*/
|
|
20
|
+
export class AdapterRouter extends LLMAdapter {
|
|
21
|
+
/** @type {Map<string, object>} modelId → provider config */
|
|
22
|
+
#modelToProvider;
|
|
23
|
+
|
|
24
|
+
/** @type {Map<string, LLMAdapter>} providerName → cached adapter */
|
|
25
|
+
#adapterCache;
|
|
26
|
+
|
|
27
|
+
/** @type {object[]} raw providers array */
|
|
28
|
+
#providers;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {{ providers: object[], config?: object }} params
|
|
32
|
+
* @param {object[]} params.providers — Array of { name, baseUrl, apiKey, protocol?, models[] }
|
|
33
|
+
*/
|
|
34
|
+
constructor({ providers }) {
|
|
35
|
+
super();
|
|
36
|
+
this.#providers = providers;
|
|
37
|
+
this.#modelToProvider = new Map();
|
|
38
|
+
this.#adapterCache = new Map();
|
|
39
|
+
|
|
40
|
+
// Build model → provider index
|
|
41
|
+
// First provider wins if model appears in multiple providers
|
|
42
|
+
for (const provider of providers) {
|
|
43
|
+
if (!Array.isArray(provider.models)) continue;
|
|
44
|
+
for (const modelId of provider.models) {
|
|
45
|
+
if (!this.#modelToProvider.has(modelId)) {
|
|
46
|
+
this.#modelToProvider.set(modelId, provider);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a model ID to its provider's adapter (lazy-created, cached).
|
|
54
|
+
*
|
|
55
|
+
* @param {string} modelId
|
|
56
|
+
* @returns {Promise<LLMAdapter>}
|
|
57
|
+
*/
|
|
58
|
+
async #resolveAdapter(modelId) {
|
|
59
|
+
const provider = this.#modelToProvider.get(modelId);
|
|
60
|
+
if (!provider) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Model "${modelId}" not found in any provider. ` +
|
|
63
|
+
`Available models: ${[...this.#modelToProvider.keys()].join(', ') || '(none)'}. ` +
|
|
64
|
+
`Check your config.json providers[].models arrays.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check cache
|
|
69
|
+
const cached = this.#adapterCache.get(provider.name);
|
|
70
|
+
if (cached) return cached;
|
|
71
|
+
|
|
72
|
+
// Create adapter based on protocol
|
|
73
|
+
const protocol = provider.protocol || 'openai';
|
|
74
|
+
let adapter;
|
|
75
|
+
|
|
76
|
+
if (protocol === 'anthropic') {
|
|
77
|
+
const { AnthropicAdapter } = await import('./anthropic.js');
|
|
78
|
+
adapter = new AnthropicAdapter({
|
|
79
|
+
apiKey: provider.apiKey,
|
|
80
|
+
baseUrl: provider.baseUrl,
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
// Default: openai (Chat Completions API) — covers proxy, OpenAI, DeepSeek, Gemini, etc.
|
|
84
|
+
const { ChatCompletionsAdapter } = await import('./chat-completions.js');
|
|
85
|
+
adapter = new ChatCompletionsAdapter({
|
|
86
|
+
apiKey: provider.apiKey,
|
|
87
|
+
baseUrl: provider.baseUrl,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.#adapterCache.set(provider.name, adapter);
|
|
92
|
+
return adapter;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Stream a model response — routes to the correct provider adapter.
|
|
97
|
+
*
|
|
98
|
+
* @param {import('./adapter.js').StreamParams} params
|
|
99
|
+
* @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
|
|
100
|
+
*/
|
|
101
|
+
async *stream(params) {
|
|
102
|
+
const adapter = await this.#resolveAdapter(params.model);
|
|
103
|
+
yield* adapter.stream(params);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Make a single model call — routes to the correct provider adapter.
|
|
108
|
+
*
|
|
109
|
+
* @param {import('./adapter.js').CallParams} params
|
|
110
|
+
* @returns {Promise<{ text: string, usage: { inputTokens: number, outputTokens: number } }>}
|
|
111
|
+
*/
|
|
112
|
+
async call(params) {
|
|
113
|
+
const adapter = await this.#resolveAdapter(params.model);
|
|
114
|
+
return adapter.call(params);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get the provider config for a given model.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} modelId
|
|
121
|
+
* @returns {object|null} — Provider config or null
|
|
122
|
+
*/
|
|
123
|
+
getProviderForModel(modelId) {
|
|
124
|
+
return this.#modelToProvider.get(modelId) || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List all available models across all providers.
|
|
129
|
+
*
|
|
130
|
+
* @returns {{ modelId: string, providerName: string }[]}
|
|
131
|
+
*/
|
|
132
|
+
listAvailableModels() {
|
|
133
|
+
const result = [];
|
|
134
|
+
for (const [modelId, provider] of this.#modelToProvider) {
|
|
135
|
+
result.push({ modelId, providerName: provider.name });
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get the raw providers config.
|
|
142
|
+
*
|
|
143
|
+
* @returns {object[]}
|
|
144
|
+
*/
|
|
145
|
+
get providers() {
|
|
146
|
+
return this.#providers;
|
|
147
|
+
}
|
|
148
|
+
}
|
package/unify/models.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* @typedef {Object} ModelInfo
|
|
17
|
-
* @property {'anthropic' | '
|
|
18
|
-
* @property {
|
|
17
|
+
* @property {'anthropic' | 'openai' | 'deepseek' | 'google'} provider — Which provider this model belongs to
|
|
18
|
+
* @property {'anthropic' | 'chat-completions'} adapter — Which wire protocol to use
|
|
19
|
+
* @property {string} baseUrl — Official API endpoint base URL
|
|
19
20
|
* @property {number} contextWindow — Max context tokens
|
|
20
21
|
* @property {number} maxOutputTokens — Max output tokens
|
|
21
22
|
* @property {string} displayName — Human-readable model name
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
export const MODEL_REGISTRY = new Map([
|
|
26
27
|
// ── Anthropic ──────────────────────────────────────────────────
|
|
27
28
|
['claude-sonnet-4-20250514', {
|
|
29
|
+
provider: 'anthropic',
|
|
28
30
|
adapter: 'anthropic',
|
|
29
31
|
baseUrl: 'https://api.anthropic.com',
|
|
30
32
|
contextWindow: 200000,
|
|
@@ -32,6 +34,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
32
34
|
displayName: 'Claude Sonnet 4',
|
|
33
35
|
}],
|
|
34
36
|
['claude-opus-4-20250514', {
|
|
37
|
+
provider: 'anthropic',
|
|
35
38
|
adapter: 'anthropic',
|
|
36
39
|
baseUrl: 'https://api.anthropic.com',
|
|
37
40
|
contextWindow: 200000,
|
|
@@ -39,6 +42,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
39
42
|
displayName: 'Claude Opus 4',
|
|
40
43
|
}],
|
|
41
44
|
['claude-haiku-3-20250414', {
|
|
45
|
+
provider: 'anthropic',
|
|
42
46
|
adapter: 'anthropic',
|
|
43
47
|
baseUrl: 'https://api.anthropic.com',
|
|
44
48
|
contextWindow: 200000,
|
|
@@ -48,6 +52,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
48
52
|
|
|
49
53
|
// ── OpenAI ─────────────────────────────────────────────────────
|
|
50
54
|
['gpt-5', {
|
|
55
|
+
provider: 'openai',
|
|
51
56
|
adapter: 'chat-completions',
|
|
52
57
|
baseUrl: 'https://api.openai.com/v1',
|
|
53
58
|
contextWindow: 256000,
|
|
@@ -55,6 +60,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
55
60
|
displayName: 'GPT-5',
|
|
56
61
|
}],
|
|
57
62
|
['gpt-5.4', {
|
|
63
|
+
provider: 'openai',
|
|
58
64
|
adapter: 'chat-completions',
|
|
59
65
|
baseUrl: 'https://api.openai.com/v1',
|
|
60
66
|
contextWindow: 272000,
|
|
@@ -62,6 +68,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
62
68
|
displayName: 'GPT-5.4',
|
|
63
69
|
}],
|
|
64
70
|
['gpt-4.1', {
|
|
71
|
+
provider: 'openai',
|
|
65
72
|
adapter: 'chat-completions',
|
|
66
73
|
baseUrl: 'https://api.openai.com/v1',
|
|
67
74
|
contextWindow: 1047576,
|
|
@@ -69,6 +76,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
69
76
|
displayName: 'GPT-4.1',
|
|
70
77
|
}],
|
|
71
78
|
['gpt-4.1-mini', {
|
|
79
|
+
provider: 'openai',
|
|
72
80
|
adapter: 'chat-completions',
|
|
73
81
|
baseUrl: 'https://api.openai.com/v1',
|
|
74
82
|
contextWindow: 1047576,
|
|
@@ -76,6 +84,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
76
84
|
displayName: 'GPT-4.1 Mini',
|
|
77
85
|
}],
|
|
78
86
|
['gpt-4.1-nano', {
|
|
87
|
+
provider: 'openai',
|
|
79
88
|
adapter: 'chat-completions',
|
|
80
89
|
baseUrl: 'https://api.openai.com/v1',
|
|
81
90
|
contextWindow: 1047576,
|
|
@@ -83,6 +92,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
83
92
|
displayName: 'GPT-4.1 Nano',
|
|
84
93
|
}],
|
|
85
94
|
['o3', {
|
|
95
|
+
provider: 'openai',
|
|
86
96
|
adapter: 'chat-completions',
|
|
87
97
|
baseUrl: 'https://api.openai.com/v1',
|
|
88
98
|
contextWindow: 200000,
|
|
@@ -90,6 +100,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
90
100
|
displayName: 'o3',
|
|
91
101
|
}],
|
|
92
102
|
['o4-mini', {
|
|
103
|
+
provider: 'openai',
|
|
93
104
|
adapter: 'chat-completions',
|
|
94
105
|
baseUrl: 'https://api.openai.com/v1',
|
|
95
106
|
contextWindow: 200000,
|
|
@@ -99,6 +110,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
99
110
|
|
|
100
111
|
// ── DeepSeek ───────────────────────────────────────────────────
|
|
101
112
|
['deepseek-chat', {
|
|
113
|
+
provider: 'deepseek',
|
|
102
114
|
adapter: 'chat-completions',
|
|
103
115
|
baseUrl: 'https://api.deepseek.com',
|
|
104
116
|
contextWindow: 131072,
|
|
@@ -106,6 +118,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
106
118
|
displayName: 'DeepSeek Chat',
|
|
107
119
|
}],
|
|
108
120
|
['deepseek-reasoner', {
|
|
121
|
+
provider: 'deepseek',
|
|
109
122
|
adapter: 'chat-completions',
|
|
110
123
|
baseUrl: 'https://api.deepseek.com',
|
|
111
124
|
contextWindow: 131072,
|
|
@@ -115,6 +128,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
115
128
|
|
|
116
129
|
// ── Google (via OpenAI-compatible API) ─────────────────────────
|
|
117
130
|
['gemini-2.5-pro', {
|
|
131
|
+
provider: 'google',
|
|
118
132
|
adapter: 'chat-completions',
|
|
119
133
|
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
120
134
|
contextWindow: 1048576,
|
|
@@ -122,6 +136,7 @@ export const MODEL_REGISTRY = new Map([
|
|
|
122
136
|
displayName: 'Gemini 2.5 Pro',
|
|
123
137
|
}],
|
|
124
138
|
['gemini-2.5-flash', {
|
|
139
|
+
provider: 'google',
|
|
125
140
|
adapter: 'chat-completions',
|
|
126
141
|
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
127
142
|
contextWindow: 1048576,
|
|
@@ -165,3 +180,32 @@ export function listModels() {
|
|
|
165
180
|
export function isKnownModel(modelName) {
|
|
166
181
|
return MODEL_REGISTRY.has(modelName);
|
|
167
182
|
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get the provider name for a model.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} modelName
|
|
188
|
+
* @returns {string|null} — Provider name ('anthropic', 'openai', 'deepseek', 'google') or null
|
|
189
|
+
*/
|
|
190
|
+
export function getProviderForModel(modelName) {
|
|
191
|
+
const info = MODEL_REGISTRY.get(modelName);
|
|
192
|
+
return info ? info.provider : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Parse a model reference in "providerName/modelId" format.
|
|
197
|
+
*
|
|
198
|
+
* @param {string} ref — e.g. "my-proxy/claude-sonnet-4-20250514" or "claude-sonnet-4-20250514"
|
|
199
|
+
* @returns {{ providerName: string|null, modelId: string }}
|
|
200
|
+
*/
|
|
201
|
+
export function parseModelRef(ref) {
|
|
202
|
+
if (!ref) return { providerName: null, modelId: '' };
|
|
203
|
+
const slashIdx = ref.indexOf('/');
|
|
204
|
+
if (slashIdx === -1) {
|
|
205
|
+
return { providerName: null, modelId: ref };
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
providerName: ref.slice(0, slashIdx),
|
|
209
|
+
modelId: ref.slice(slashIdx + 1),
|
|
210
|
+
};
|
|
211
|
+
}
|
package/unify/session.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* a fully wired Session ready for queries.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { initYeaftDir } from './init.js';
|
|
16
|
+
import { initYeaftDir, DEFAULT_YEAFT_DIR } from './init.js';
|
|
17
17
|
import { loadConfig, loadMCPConfig } from './config.js';
|
|
18
18
|
import { createTrace } from './debug-trace.js';
|
|
19
19
|
import { createLLMAdapter } from './llm/adapter.js';
|
|
@@ -46,6 +46,7 @@ import exitWorktree from './tools/exit-worktree.js';
|
|
|
46
46
|
/**
|
|
47
47
|
* @typedef {Object} Session
|
|
48
48
|
* @property {Engine} engine — The wired engine, ready for .query()
|
|
49
|
+
* @property {import('./llm/adapter.js').LLMAdapter} adapter — The LLM adapter
|
|
49
50
|
* @property {object} config — Resolved configuration
|
|
50
51
|
* @property {ConversationStore} conversationStore — Conversation persistence
|
|
51
52
|
* @property {MemoryStore} memoryStore — Memory persistence
|
|
@@ -80,18 +81,21 @@ export async function loadSession(options = {}) {
|
|
|
80
81
|
configOverrides = {},
|
|
81
82
|
} = options;
|
|
82
83
|
|
|
83
|
-
// ─── 1.
|
|
84
|
+
// ─── 1. Determine yeaftDir + ensure directory structure ──
|
|
85
|
+
// Must happen BEFORE loadConfig so that first-run
|
|
86
|
+
// generates a default config.json that loadConfig can read.
|
|
84
87
|
const overrides = { ...configOverrides };
|
|
85
88
|
if (dir) overrides.dir = dir;
|
|
86
89
|
if (model) overrides.model = model;
|
|
87
90
|
if (language) overrides.language = language;
|
|
88
91
|
if (debug !== undefined) overrides.debug = debug;
|
|
89
92
|
|
|
90
|
-
const
|
|
91
|
-
const yeaftDir = config.dir;
|
|
92
|
-
|
|
93
|
-
// ─── 2. Ensure directory structure ─────────────────────
|
|
93
|
+
const yeaftDir = overrides.dir || process.env.YEAFT_DIR || DEFAULT_YEAFT_DIR;
|
|
94
94
|
initYeaftDir(yeaftDir);
|
|
95
|
+
overrides.dir = yeaftDir;
|
|
96
|
+
|
|
97
|
+
// ─── 2. Load config ───────────────────────────────────
|
|
98
|
+
const config = loadConfig(overrides);
|
|
95
99
|
|
|
96
100
|
// ─── 3. Create debug trace ─────────────────────────────
|
|
97
101
|
const trace = createTrace({
|
|
@@ -177,6 +181,7 @@ export async function loadSession(options = {}) {
|
|
|
177
181
|
|
|
178
182
|
return {
|
|
179
183
|
engine,
|
|
184
|
+
adapter,
|
|
180
185
|
config,
|
|
181
186
|
conversationStore,
|
|
182
187
|
memoryStore,
|
package/unify/stop-hooks.js
CHANGED
|
@@ -23,6 +23,7 @@ import { checkDreamGate, incrementQueryCount, dream } from './memory/dream.js';
|
|
|
23
23
|
* memoryStore: import('./memory/store.js').MemoryStore,
|
|
24
24
|
* adapter: object,
|
|
25
25
|
* config: object,
|
|
26
|
+
* primaryModel?: string,
|
|
26
27
|
* messages?: object[],
|
|
27
28
|
* taskId?: string,
|
|
28
29
|
* workerId?: string,
|
|
@@ -38,11 +39,15 @@ export async function runStopHooks(context) {
|
|
|
38
39
|
memoryStore,
|
|
39
40
|
adapter,
|
|
40
41
|
config,
|
|
42
|
+
primaryModel,
|
|
41
43
|
messages = [],
|
|
42
44
|
taskId,
|
|
43
45
|
trace,
|
|
44
46
|
} = context;
|
|
45
47
|
|
|
48
|
+
// Model name for persisted messages: use primaryModel if provided, else config.model
|
|
49
|
+
const persistModel = primaryModel || config?.model;
|
|
50
|
+
|
|
46
51
|
const result = {
|
|
47
52
|
messagesPersisted: 0,
|
|
48
53
|
consolidated: false,
|
|
@@ -65,7 +70,7 @@ export async function runStopHooks(context) {
|
|
|
65
70
|
role: msg.role,
|
|
66
71
|
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content),
|
|
67
72
|
mode,
|
|
68
|
-
model:
|
|
73
|
+
model: persistModel,
|
|
69
74
|
});
|
|
70
75
|
result.messagesPersisted++;
|
|
71
76
|
}
|