shennian 0.2.88 → 0.2.90
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/dist/assets/wechat-channel/macos/manifest.json +22 -0
- package/dist/assets/wechat-channel/macos/shennian-wechat-channel-helper +0 -0
- package/dist/bin/shennian.js +1 -1
- package/dist/publish-build-manifest.json +548 -0
- package/dist/scripts/wechat-rpa-confirmation.mjs +5 -97
- package/dist/src/agent-env.js +4 -105
- package/dist/src/agents/adapter.d.ts +6 -0
- package/dist/src/agents/adapter.js +1 -19
- package/dist/src/agents/claude.js +8 -305
- package/dist/src/agents/codex-control.d.ts +35 -0
- package/dist/src/agents/codex-control.js +2 -0
- package/dist/src/agents/codex-utils.js +7 -200
- package/dist/src/agents/codex.d.ts +8 -0
- package/dist/src/agents/codex.js +15 -863
- package/dist/src/agents/command-spec.js +2 -413
- package/dist/src/agents/config-status.js +1 -226
- package/dist/src/agents/cursor.js +1 -249
- package/dist/src/agents/custom.js +4 -271
- package/dist/src/agents/detect.js +1 -56
- package/dist/src/agents/external-channel-instructions.js +10 -94
- package/dist/src/agents/gemini.js +1 -173
- package/dist/src/agents/manager.js +13 -157
- package/dist/src/agents/model-registry/cache.js +1 -37
- package/dist/src/agents/model-registry/discovery.js +2 -187
- package/dist/src/agents/model-registry/parsers.js +4 -447
- package/dist/src/agents/model-registry/runner.js +1 -30
- package/dist/src/agents/model-registry/service.js +1 -78
- package/dist/src/agents/model-registry/types.js +1 -8
- package/dist/src/agents/model-registry.js +1 -18
- package/dist/src/agents/openclaw.js +2 -275
- package/dist/src/agents/opencode.js +1 -231
- package/dist/src/agents/pi-context.js +12 -217
- package/dist/src/agents/pi.js +14 -723
- package/dist/src/agents/platform-instructions.js +9 -54
- package/dist/src/channels/base.d.ts +4 -1
- package/dist/src/channels/base.js +1 -3
- package/dist/src/channels/registry.js +1 -30
- package/dist/src/channels/reply-split.js +10 -89
- package/dist/src/channels/runtime.d.ts +1 -0
- package/dist/src/channels/runtime.js +5 -533
- package/dist/src/channels/secret-registry.d.ts +1 -0
- package/dist/src/channels/secret-registry.js +1 -46
- package/dist/src/channels/websocket.js +8 -378
- package/dist/src/channels/wechat-channel/anchor.d.ts +10 -0
- package/dist/src/channels/wechat-channel/anchor.js +1 -0
- package/dist/src/channels/wechat-channel/client.d.ts +74 -0
- package/dist/src/channels/wechat-channel/client.js +1 -0
- package/dist/src/channels/wechat-channel/cooldown.d.ts +15 -0
- package/dist/src/channels/wechat-channel/cooldown.js +1 -0
- package/dist/src/channels/wechat-channel/fingerprint.d.ts +28 -0
- package/dist/src/channels/wechat-channel/fingerprint.js +1 -0
- package/dist/src/channels/wechat-channel/helper-assets.d.ts +37 -0
- package/dist/src/channels/wechat-channel/helper-assets.js +1 -0
- package/dist/src/channels/wechat-channel/helper-client.d.ts +25 -0
- package/dist/src/channels/wechat-channel/helper-client.js +3 -0
- package/dist/src/channels/wechat-channel/helper-protocol.d.ts +84 -0
- package/dist/src/channels/wechat-channel/helper-protocol.js +1 -0
- package/dist/src/channels/wechat-channel/index.d.ts +17 -0
- package/dist/src/channels/wechat-channel/index.js +1 -0
- package/dist/src/channels/wechat-channel/ledger.d.ts +33 -0
- package/dist/src/channels/wechat-channel/ledger.js +1 -0
- package/dist/src/channels/wechat-channel/media-resolver.d.ts +32 -0
- package/dist/src/channels/wechat-channel/media-resolver.js +1 -0
- package/dist/src/channels/wechat-channel/message-key.d.ts +19 -0
- package/dist/src/channels/wechat-channel/message-key.js +1 -0
- package/dist/src/channels/wechat-channel/observer.d.ts +64 -0
- package/dist/src/channels/wechat-channel/observer.js +1 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.d.ts +69 -0
- package/dist/src/channels/wechat-channel/outbound-ledger.js +2 -0
- package/dist/src/channels/wechat-channel/outbound-sender.d.ts +26 -0
- package/dist/src/channels/wechat-channel/outbound-sender.js +1 -0
- package/dist/src/channels/wechat-channel/preflight.d.ts +37 -0
- package/dist/src/channels/wechat-channel/preflight.js +1 -0
- package/dist/src/channels/wechat-channel/runner.d.ts +34 -0
- package/dist/src/channels/wechat-channel/runner.js +1 -0
- package/dist/src/channels/wechat-channel/runtime.d.ts +45 -0
- package/dist/src/channels/wechat-channel/runtime.js +1 -0
- package/dist/src/channels/wechat-channel/scheduler.d.ts +35 -0
- package/dist/src/channels/wechat-channel/scheduler.js +1 -0
- package/dist/src/channels/wechat-rpa/macos-flow.js +1 -96
- package/dist/src/channels/wechat-rpa/macos.js +6 -48
- package/dist/src/channels/wechat-rpa/normalizer.js +7 -127
- package/dist/src/channels/wechat-rpa.d.ts +21 -0
- package/dist/src/channels/wechat-rpa.js +6 -1022
- package/dist/src/channels/wecom.js +4 -357
- package/dist/src/commands/agent.js +6 -131
- package/dist/src/commands/daemon-windows.js +8 -48
- package/dist/src/commands/daemon.js +19 -1013
- package/dist/src/commands/external-attachments.js +1 -51
- package/dist/src/commands/external.js +1 -137
- package/dist/src/commands/manager.js +2 -389
- package/dist/src/commands/pair-qr.js +1 -6
- package/dist/src/commands/pair.js +9 -287
- package/dist/src/commands/tools.js +1 -34
- package/dist/src/commands/upgrade.js +1 -198
- package/dist/src/config/index.js +1 -35
- package/dist/src/daemon-log.js +6 -58
- package/dist/src/env-path.js +1 -64
- package/dist/src/fs/boundary.js +1 -126
- package/dist/src/fs/handler.js +1 -130
- package/dist/src/fs/security.js +1 -32
- package/dist/src/fs/text-decoder.d.ts +10 -0
- package/dist/src/fs/text-decoder.js +1 -0
- package/dist/src/index.js +2 -404
- package/dist/src/log-reporter.js +1 -16
- package/dist/src/manager/prompt.js +29 -34
- package/dist/src/manager/registry.js +2 -269
- package/dist/src/manager/runtime.js +19 -1003
- package/dist/src/native-fusion/config.js +1 -5
- package/dist/src/native-fusion/opencode-parser.js +3 -123
- package/dist/src/native-fusion/parser-common.js +8 -264
- package/dist/src/native-fusion/parsers.js +8 -729
- package/dist/src/native-fusion/service.d.ts +10 -0
- package/dist/src/native-fusion/service.js +2 -198
- package/dist/src/native-fusion/state.js +1 -22
- package/dist/src/native-fusion/types.js +1 -1
- package/dist/src/region.js +1 -88
- package/dist/src/relay/client.js +1 -343
- package/dist/src/session/archive-zip.js +1 -220
- package/dist/src/session/handlers/agent-config.js +1 -150
- package/dist/src/session/handlers/agents.js +1 -55
- package/dist/src/session/handlers/chat.js +2 -733
- package/dist/src/session/handlers/control.js +1 -55
- package/dist/src/session/handlers/fs.js +1 -747
- package/dist/src/session/handlers/session-refresh.js +1 -35
- package/dist/src/session/handlers/skills.js +1 -121
- package/dist/src/session/handlers/title.js +1 -60
- package/dist/src/session/handlers/tool-detail.d.ts +3 -0
- package/dist/src/session/handlers/tool-detail.js +1 -0
- package/dist/src/session/manager.d.ts +3 -0
- package/dist/src/session/manager.js +1 -261
- package/dist/src/session/projection.js +1 -54
- package/dist/src/session/queue.js +4 -317
- package/dist/src/session/remote-attachments.js +1 -72
- package/dist/src/session/store.js +3 -109
- package/dist/src/session/types.d.ts +4 -0
- package/dist/src/session/types.js +1 -4
- package/dist/src/skills/registry.js +15 -148
- package/dist/src/skills/setup.js +1 -101
- package/dist/src/tools/markdown-to-pdf.js +10 -346
- package/dist/src/upgrade/engine.js +3 -347
- package/package.json +3 -2
- package/dist/scripts/wechat-rpa-download-candidates.mjs +0 -105
|
@@ -1,30 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/model-switching.test.ts
|
|
3
|
-
import { spawnResolvedCommand } from '../command-spec.js';
|
|
4
|
-
import { DISCOVERY_WORKDIR } from './types.js';
|
|
5
|
-
import { buildAgentProcessEnv } from '../../agent-env.js';
|
|
6
|
-
export function runResolvedCommand(spec, args, timeoutMs = 15_000) {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
const proc = spawnResolvedCommand(spec, args, {
|
|
9
|
-
cwd: DISCOVERY_WORKDIR,
|
|
10
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
11
|
-
env: buildAgentProcessEnv({ NO_COLOR: '1' }),
|
|
12
|
-
});
|
|
13
|
-
let stdout = '';
|
|
14
|
-
let stderr = '';
|
|
15
|
-
const timer = setTimeout(() => {
|
|
16
|
-
proc.kill('SIGKILL');
|
|
17
|
-
}, timeoutMs);
|
|
18
|
-
proc.stdout?.on('data', (chunk) => {
|
|
19
|
-
stdout += chunk.toString();
|
|
20
|
-
});
|
|
21
|
-
proc.stderr?.on('data', (chunk) => {
|
|
22
|
-
stderr += chunk.toString();
|
|
23
|
-
});
|
|
24
|
-
proc.on('error', reject);
|
|
25
|
-
proc.on('close', (exitCode) => {
|
|
26
|
-
clearTimeout(timer);
|
|
27
|
-
resolve({ stdout, stderr, exitCode });
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
1
|
+
import{spawnResolvedCommand as l}from"../command-spec.js";import{DISCOVERY_WORKDIR as a}from"./types.js";import{buildAgentProcessEnv as c}from"../../agent-env.js";function f(n,i,s=15e3){return new Promise((m,d)=>{const o=l(n,i,{cwd:a,stdio:["ignore","pipe","pipe"],env:c({NO_COLOR:"1"})});let e="",r="";const p=setTimeout(()=>{o.kill("SIGKILL")},s);o.stdout?.on("data",t=>{e+=t.toString()}),o.stderr?.on("data",t=>{r+=t.toString()}),o.on("error",d),o.on("close",t=>{clearTimeout(p),m({stdout:e,stderr:r,exitCode:t})})})}export{f as runResolvedCommand};
|
|
@@ -1,78 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/model-switching.test.ts
|
|
3
|
-
import { getAgentConfigSummary } from '../config-status.js';
|
|
4
|
-
import { isCacheFresh, readCache, writeCache } from './cache.js';
|
|
5
|
-
import { discoverModelsForAgent } from './discovery.js';
|
|
6
|
-
function uniqueModels(models) {
|
|
7
|
-
const seen = new Set();
|
|
8
|
-
const unique = [];
|
|
9
|
-
for (const model of models) {
|
|
10
|
-
if (!model.id || seen.has(model.id))
|
|
11
|
-
continue;
|
|
12
|
-
seen.add(model.id);
|
|
13
|
-
unique.push(model);
|
|
14
|
-
}
|
|
15
|
-
return unique;
|
|
16
|
-
}
|
|
17
|
-
export function getCachedAgentInfos(detectedAgents) {
|
|
18
|
-
const cache = readCache();
|
|
19
|
-
return detectedAgents.map((agent) => {
|
|
20
|
-
const config = getAgentConfigSummary(agent.type);
|
|
21
|
-
return {
|
|
22
|
-
type: agent.type,
|
|
23
|
-
models: cache.agents[agent.type]?.models ?? agent.models ?? [],
|
|
24
|
-
...(config ? { config } : {}),
|
|
25
|
-
};
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export async function refreshAgentInfos(detectedAgents, context) {
|
|
29
|
-
const cache = readCache();
|
|
30
|
-
for (const agent of detectedAgents) {
|
|
31
|
-
delete cache.agents[agent.type];
|
|
32
|
-
}
|
|
33
|
-
writeCache(cache);
|
|
34
|
-
return resolveAgentInfos(detectedAgents, context);
|
|
35
|
-
}
|
|
36
|
-
export async function resolveAgentInfos(detectedAgents, context) {
|
|
37
|
-
const cache = readCache();
|
|
38
|
-
let changed = false;
|
|
39
|
-
const infos = [];
|
|
40
|
-
for (const agent of detectedAgents) {
|
|
41
|
-
const cached = cache.agents[agent.type];
|
|
42
|
-
let models;
|
|
43
|
-
if (isCacheFresh(cached, agent.version)) {
|
|
44
|
-
models = cached.models;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
let discovered = null;
|
|
48
|
-
try {
|
|
49
|
-
discovered = uniqueModels(await discoverModelsForAgent(agent, context));
|
|
50
|
-
models = discovered.length > 0 ? discovered : cached?.models ?? agent.models ?? [];
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
models = cached?.models ?? agent.models ?? [];
|
|
54
|
-
}
|
|
55
|
-
if (discovered && discovered.length > 0) {
|
|
56
|
-
const nextEntry = {
|
|
57
|
-
version: agent.version,
|
|
58
|
-
refreshedAt: Date.now(),
|
|
59
|
-
models,
|
|
60
|
-
};
|
|
61
|
-
if (JSON.stringify(cached) !== JSON.stringify(nextEntry)) {
|
|
62
|
-
cache.agents[agent.type] = nextEntry;
|
|
63
|
-
changed = true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const config = getAgentConfigSummary(agent.type);
|
|
68
|
-
infos.push({
|
|
69
|
-
type: agent.type,
|
|
70
|
-
models,
|
|
71
|
-
...(config ? { config } : {}),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
if (changed) {
|
|
75
|
-
writeCache(cache);
|
|
76
|
-
}
|
|
77
|
-
return infos;
|
|
78
|
-
}
|
|
1
|
+
import{getAgentConfigSummary as m}from"../config-status.js";import{isCacheFresh as u,readCache as f,writeCache as p}from"./cache.js";import{discoverModelsForAgent as h}from"./discovery.js";function g(o){const s=new Set,e=[];for(const t of o)!t.id||s.has(t.id)||(s.add(t.id),e.push(t));return e}function w(o){const s=f();return o.map(e=>{const t=m(e.type);return{type:e.type,models:s.agents[e.type]?.models??e.models??[],...t?{config:t}:{}}})}async function x(o,s){const e=f();for(const t of o)delete e.agents[t.type];return p(e),y(o,s)}async function y(o,s){const e=f();let t=!1;const a=[];for(const n of o){const r=e.agents[n.type];let c;if(u(r,n.version))c=r.models;else{let i=null;try{i=g(await h(n,s)),c=i.length>0?i:r?.models??n.models??[]}catch{c=r?.models??n.models??[]}if(i&&i.length>0){const l={version:n.version,refreshedAt:Date.now(),models:c};JSON.stringify(r)!==JSON.stringify(l)&&(e.agents[n.type]=l,t=!0)}}const d=m(n.type);a.push({type:n.type,models:c,...d?{config:d}:{}})}return t&&p(e),a}export{w as getCachedAgentInfos,x as refreshAgentInfos,y as resolveAgentInfos};
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/model-switching.test.ts
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import { resolveShennianPath } from '../../config/index.js';
|
|
5
|
-
export const CACHE_VERSION = 1;
|
|
6
|
-
export const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
7
|
-
export const MODEL_CACHE_PATH = resolveShennianPath('model-cache.json');
|
|
8
|
-
export const DISCOVERY_WORKDIR = os.tmpdir();
|
|
1
|
+
import o from"node:os";import{resolveShennianPath as t}from"../../config/index.js";const n=1,p=1440*60*1e3,s=t("model-cache.json"),C=o.tmpdir();export{p as CACHE_TTL_MS,n as CACHE_VERSION,C as DISCOVERY_WORKDIR,s as MODEL_CACHE_PATH};
|
|
@@ -1,18 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/model-switching.test.ts
|
|
3
|
-
export { getCachedAgentInfos, refreshAgentInfos, resolveAgentInfos, } from './model-registry/service.js';
|
|
4
|
-
import { applyClaudeModelEnvOverrides, discoverClaudeAliasModelsFromEnv, fallbackClaudeAliasModels, parseClaudeBinaryModels, parseClaudeModels, parseCodexModels, parseCodexAppServerModels, parseCursorModels, parseGeminiModels, parseOpenClawModels, stripAnsi, } from './model-registry/parsers.js';
|
|
5
|
-
/** Used by Vitest / `model-switching-e2e.ts` — not a stable public API. */
|
|
6
|
-
export const modelSwitchingTestExports = {
|
|
7
|
-
applyClaudeModelEnvOverrides,
|
|
8
|
-
discoverClaudeAliasModelsFromEnv,
|
|
9
|
-
fallbackClaudeAliasModels,
|
|
10
|
-
parseClaudeBinaryModels,
|
|
11
|
-
parseClaudeModels,
|
|
12
|
-
parseCodexModels,
|
|
13
|
-
parseCodexAppServerModels,
|
|
14
|
-
parseCursorModels,
|
|
15
|
-
parseOpenClawModels,
|
|
16
|
-
parseGeminiModels,
|
|
17
|
-
stripAnsi,
|
|
18
|
-
};
|
|
1
|
+
import{getCachedAgentInfos as m,refreshAgentInfos as u,resolveAgentInfos as v}from"./model-registry/service.js";import{applyClaudeModelEnvOverrides as e,discoverClaudeAliasModelsFromEnv as s,fallbackClaudeAliasModels as o,parseClaudeBinaryModels as r,parseClaudeModels as l,parseCodexModels as d,parseCodexAppServerModels as a,parseCursorModels as p,parseGeminiModels as n,parseOpenClawModels as i,stripAnsi as t}from"./model-registry/parsers.js";const M={applyClaudeModelEnvOverrides:e,discoverClaudeAliasModelsFromEnv:s,fallbackClaudeAliasModels:o,parseClaudeBinaryModels:r,parseClaudeModels:l,parseCodexModels:d,parseCodexAppServerModels:a,parseCursorModels:p,parseOpenClawModels:i,parseGeminiModels:n,stripAnsi:t};export{m as getCachedAgentInfos,M as modelSwitchingTestExports,u as refreshAgentInfos,v as resolveAgentInfos};
|
|
@@ -1,275 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { resolveBuiltinCommand, spawnResolvedCommand } from './command-spec.js';
|
|
4
|
-
import { buildAgentProcessEnv } from '../agent-env.js';
|
|
5
|
-
/** Best-effort parse of `openclaw agent --json` stdout; shape may vary by version. */
|
|
6
|
-
function extractResponseText(parsed) {
|
|
7
|
-
if (parsed == null)
|
|
8
|
-
return '';
|
|
9
|
-
if (typeof parsed === 'string')
|
|
10
|
-
return parsed;
|
|
11
|
-
if (typeof parsed === 'object') {
|
|
12
|
-
const o = parsed;
|
|
13
|
-
// Primary format: { payloads: [{ text: "..." }] }
|
|
14
|
-
if (Array.isArray(o.payloads)) {
|
|
15
|
-
const parts = [];
|
|
16
|
-
for (const p of o.payloads) {
|
|
17
|
-
if (p && typeof p === 'object') {
|
|
18
|
-
const t = p.text;
|
|
19
|
-
if (typeof t === 'string')
|
|
20
|
-
parts.push(t);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
if (parts.join('').trim())
|
|
24
|
-
return parts.join('\n');
|
|
25
|
-
}
|
|
26
|
-
const direct = o.response ?? o.message ?? o.text ?? o.output ?? o.content ?? o.result;
|
|
27
|
-
if (typeof direct === 'string' && direct.trim())
|
|
28
|
-
return direct;
|
|
29
|
-
}
|
|
30
|
-
return '';
|
|
31
|
-
}
|
|
32
|
-
function extractSessionId(parsed) {
|
|
33
|
-
if (parsed == null || typeof parsed !== 'object')
|
|
34
|
-
return undefined;
|
|
35
|
-
const o = parsed;
|
|
36
|
-
const meta = o.meta;
|
|
37
|
-
const agentMeta = meta?.agentMeta;
|
|
38
|
-
const id = agentMeta?.sessionId ?? o.sessionId ?? o.session_id;
|
|
39
|
-
return typeof id === 'string' && id ? id : undefined;
|
|
40
|
-
}
|
|
41
|
-
function extractUsage(parsed) {
|
|
42
|
-
if (parsed == null || typeof parsed !== 'object')
|
|
43
|
-
return undefined;
|
|
44
|
-
const o = parsed;
|
|
45
|
-
// Primary: meta.agentMeta.usage (openclaw format)
|
|
46
|
-
const meta = o.meta;
|
|
47
|
-
const agentMeta = meta?.agentMeta;
|
|
48
|
-
const u = agentMeta?.usage ?? o.usage;
|
|
49
|
-
if (!u || typeof u !== 'object')
|
|
50
|
-
return undefined;
|
|
51
|
-
const rec = u;
|
|
52
|
-
const inTok = rec.input ?? rec.inputTokens ?? rec.input_tokens;
|
|
53
|
-
const outTok = rec.output ?? rec.outputTokens ?? rec.output_tokens;
|
|
54
|
-
if (typeof inTok !== 'number' && typeof outTok !== 'number')
|
|
55
|
-
return undefined;
|
|
56
|
-
return {
|
|
57
|
-
inputTokens: typeof inTok === 'number' ? inTok : 0,
|
|
58
|
-
outputTokens: typeof outTok === 'number' ? outTok : 0,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
function extractErrorMessage(parsed) {
|
|
62
|
-
if (parsed == null || typeof parsed !== 'object')
|
|
63
|
-
return undefined;
|
|
64
|
-
const o = parsed;
|
|
65
|
-
const err = o.error ?? o.err ?? o.errorMessage;
|
|
66
|
-
if (typeof err === 'string' && err.trim())
|
|
67
|
-
return err;
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
export class OpenClawAdapter extends AgentAdapter {
|
|
71
|
-
type = 'openclaw';
|
|
72
|
-
process = null;
|
|
73
|
-
sessionId = null;
|
|
74
|
-
/** OpenClaw session id from JSON or `resume()`; falls back to app `sessionId`. */
|
|
75
|
-
agentSessionId = null;
|
|
76
|
-
currentModelId = null;
|
|
77
|
-
workDir = null;
|
|
78
|
-
seq = 0;
|
|
79
|
-
runId = '';
|
|
80
|
-
async start(sessionId, workDir, agentSessionId) {
|
|
81
|
-
this.sessionId = sessionId;
|
|
82
|
-
this.workDir = workDir;
|
|
83
|
-
this.seq = 0;
|
|
84
|
-
if (agentSessionId)
|
|
85
|
-
this.agentSessionId = agentSessionId;
|
|
86
|
-
}
|
|
87
|
-
async send(text, modelId) {
|
|
88
|
-
await this.killProcess();
|
|
89
|
-
this.runId = randomUUID();
|
|
90
|
-
this.seq = 0;
|
|
91
|
-
const sid = this.agentSessionId ?? this.sessionId;
|
|
92
|
-
if (!sid) {
|
|
93
|
-
this.emitEvent({ state: 'error', message: 'openclaw: session not started' });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
// OpenClaw `openclaw agent` uses session-scoped model; `--model` on the same CLI may not apply here.
|
|
97
|
-
// Sending `/model <id>` as a message matches interactive `/model` and updates the session before the user turn.
|
|
98
|
-
if (modelId && modelId !== this.currentModelId) {
|
|
99
|
-
const modelSwitch = await this.runAgentCommand(['--message', `/model ${modelId}`, '--session-id', sid]);
|
|
100
|
-
if (!modelSwitch.ok) {
|
|
101
|
-
this.emitEvent({ state: 'error', message: modelSwitch.error ?? 'openclaw model switch failed' });
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
this.currentModelId = modelId;
|
|
105
|
-
}
|
|
106
|
-
this.spawnAgent(['--message', text, '--session-id', sid]);
|
|
107
|
-
}
|
|
108
|
-
async resume(agentSessionId) {
|
|
109
|
-
await this.killProcess();
|
|
110
|
-
this.agentSessionId = agentSessionId;
|
|
111
|
-
this.runId = randomUUID();
|
|
112
|
-
this.seq = 0;
|
|
113
|
-
// No new user turn; minimal body so the CLI accepts the invocation.
|
|
114
|
-
this.spawnAgent(['--message', '\u200b', '--session-id', agentSessionId]);
|
|
115
|
-
}
|
|
116
|
-
async stop() {
|
|
117
|
-
await this.killProcess();
|
|
118
|
-
}
|
|
119
|
-
spawnAgent(extraArgs) {
|
|
120
|
-
const spec = resolveBuiltinCommand('openclaw');
|
|
121
|
-
if (!spec) {
|
|
122
|
-
this.emit('error', new Error('Command "openclaw" not found. Is OpenClaw CLI installed?'));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const args = ['agent', '--local', '--json', ...extraArgs];
|
|
126
|
-
const proc = spawnResolvedCommand(spec, args, {
|
|
127
|
-
cwd: this.workDir ?? undefined,
|
|
128
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
129
|
-
env: buildAgentProcessEnv(),
|
|
130
|
-
});
|
|
131
|
-
this.process = proc;
|
|
132
|
-
let stdoutBuf = '';
|
|
133
|
-
proc.stdout?.on('data', (chunk) => {
|
|
134
|
-
stdoutBuf += chunk.toString();
|
|
135
|
-
});
|
|
136
|
-
let stderrBuf = '';
|
|
137
|
-
proc.stderr?.on('data', (chunk) => {
|
|
138
|
-
stderrBuf += chunk.toString();
|
|
139
|
-
});
|
|
140
|
-
proc.on('close', (code) => {
|
|
141
|
-
if (this.process !== proc)
|
|
142
|
-
return;
|
|
143
|
-
this.process = null;
|
|
144
|
-
if (code !== 0 && code !== null) {
|
|
145
|
-
const msg = stderrBuf.trim() || `openclaw exited with code ${code}`;
|
|
146
|
-
this.emitEvent({ state: 'error', message: msg });
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
const raw = stdoutBuf.trim();
|
|
150
|
-
if (!raw) {
|
|
151
|
-
const msg = stderrBuf.trim() || 'openclaw returned empty stdout';
|
|
152
|
-
this.emitEvent({ state: 'error', message: msg });
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
let parsed;
|
|
156
|
-
try {
|
|
157
|
-
parsed = JSON.parse(raw);
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
this.emitEvent({
|
|
161
|
-
state: 'error',
|
|
162
|
-
message: `openclaw stdout is not valid JSON: ${raw.slice(0, 200)}`,
|
|
163
|
-
});
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
const rec = parsed;
|
|
167
|
-
const failed = rec.ok === false || rec.success === false;
|
|
168
|
-
if (failed) {
|
|
169
|
-
this.emitEvent({
|
|
170
|
-
state: 'error',
|
|
171
|
-
message: extractErrorMessage(parsed) ?? 'openclaw request failed',
|
|
172
|
-
});
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
const nextSid = extractSessionId(parsed);
|
|
176
|
-
if (nextSid)
|
|
177
|
-
this.agentSessionId = nextSid;
|
|
178
|
-
const text = extractResponseText(parsed);
|
|
179
|
-
if (text) {
|
|
180
|
-
this.emitEvent({ state: 'delta', text });
|
|
181
|
-
}
|
|
182
|
-
const usage = extractUsage(parsed);
|
|
183
|
-
this.emitEvent({ state: 'final', agentSessionId: this.agentSessionId ?? undefined, usage });
|
|
184
|
-
});
|
|
185
|
-
proc.on('error', (err) => {
|
|
186
|
-
if (this.process !== proc)
|
|
187
|
-
return;
|
|
188
|
-
this.process = null;
|
|
189
|
-
if (err.code === 'ENOENT') {
|
|
190
|
-
this.emit('error', new Error('Command "openclaw" not found. Is OpenClaw CLI installed?'));
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
this.emit('error', err);
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
async runAgentCommand(extraArgs) {
|
|
198
|
-
return new Promise((resolve) => {
|
|
199
|
-
const spec = resolveBuiltinCommand('openclaw');
|
|
200
|
-
if (!spec) {
|
|
201
|
-
resolve({ ok: false, error: 'Command "openclaw" not found. Is OpenClaw CLI installed?' });
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const args = ['agent', '--local', '--json', ...extraArgs];
|
|
205
|
-
const proc = spawnResolvedCommand(spec, args, {
|
|
206
|
-
cwd: this.workDir ?? undefined,
|
|
207
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
208
|
-
env: buildAgentProcessEnv(),
|
|
209
|
-
});
|
|
210
|
-
let stdoutBuf = '';
|
|
211
|
-
let stderrBuf = '';
|
|
212
|
-
proc.stdout?.on('data', (chunk) => {
|
|
213
|
-
stdoutBuf += chunk.toString();
|
|
214
|
-
});
|
|
215
|
-
proc.stderr?.on('data', (chunk) => {
|
|
216
|
-
stderrBuf += chunk.toString();
|
|
217
|
-
});
|
|
218
|
-
proc.on('close', (code) => {
|
|
219
|
-
if (code !== 0 && code !== null) {
|
|
220
|
-
resolve({ ok: false, error: stderrBuf.trim() || `openclaw exited with code ${code}` });
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const raw = stdoutBuf.trim();
|
|
224
|
-
if (!raw) {
|
|
225
|
-
resolve({ ok: true });
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
try {
|
|
229
|
-
const parsed = JSON.parse(raw);
|
|
230
|
-
if (parsed.ok === false || parsed.success === false) {
|
|
231
|
-
resolve({
|
|
232
|
-
ok: false,
|
|
233
|
-
error: extractErrorMessage(parsed) ?? 'openclaw request failed',
|
|
234
|
-
});
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const nextSid = extractSessionId(parsed);
|
|
238
|
-
if (nextSid)
|
|
239
|
-
this.agentSessionId = nextSid;
|
|
240
|
-
resolve({ ok: true, parsed });
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
resolve({ ok: false, error: `openclaw stdout is not valid JSON: ${raw.slice(0, 200)}` });
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
proc.on('error', (err) => {
|
|
247
|
-
resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
emitEvent(partial) {
|
|
252
|
-
const event = {
|
|
253
|
-
...partial,
|
|
254
|
-
runId: this.runId,
|
|
255
|
-
seq: this.seq++,
|
|
256
|
-
};
|
|
257
|
-
this.emit('agentEvent', event);
|
|
258
|
-
}
|
|
259
|
-
async killProcess() {
|
|
260
|
-
const proc = this.process;
|
|
261
|
-
if (!proc)
|
|
262
|
-
return;
|
|
263
|
-
this.process = null;
|
|
264
|
-
proc.kill('SIGTERM');
|
|
265
|
-
await new Promise((resolve) => {
|
|
266
|
-
proc.on('close', resolve);
|
|
267
|
-
setTimeout(() => {
|
|
268
|
-
proc.kill('SIGKILL');
|
|
269
|
-
resolve();
|
|
270
|
-
}, 3000);
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
// OpenClaw support is intentionally disabled.
|
|
275
|
-
// registerAgent('openclaw', () => new OpenClawAdapter())
|
|
1
|
+
import{randomUUID as g}from"node:crypto";import{AgentAdapter as E}from"./adapter.js";import{resolveBuiltinCommand as h,spawnResolvedCommand as w}from"./command-spec.js";import{buildAgentProcessEnv as y}from"../agent-env.js";function x(n){if(n==null)return"";if(typeof n=="string")return n;if(typeof n=="object"){const t=n;if(Array.isArray(t.payloads)){const o=[];for(const s of t.payloads)if(s&&typeof s=="object"){const r=s.text;typeof r=="string"&&o.push(r)}if(o.join("").trim())return o.join(`
|
|
2
|
+
`)}const e=t.response??t.message??t.text??t.output??t.content??t.result;if(typeof e=="string"&&e.trim())return e}return""}function k(n){if(n==null||typeof n!="object")return;const t=n,s=t.meta?.agentMeta?.sessionId??t.sessionId??t.session_id;return typeof s=="string"&&s?s:void 0}function C(n){if(n==null||typeof n!="object")return;const t=n,s=t.meta?.agentMeta?.usage??t.usage;if(!s||typeof s!="object")return;const r=s,c=r.input??r.inputTokens??r.input_tokens,i=r.output??r.outputTokens??r.output_tokens;if(!(typeof c!="number"&&typeof i!="number"))return{inputTokens:typeof c=="number"?c:0,outputTokens:typeof i=="number"?i:0}}function I(n){if(n==null||typeof n!="object")return;const t=n,e=t.error??t.err??t.errorMessage;if(typeof e=="string"&&e.trim())return e}class j extends E{type="openclaw";process=null;sessionId=null;agentSessionId=null;currentModelId=null;workDir=null;seq=0;runId="";async start(t,e,o){this.sessionId=t,this.workDir=e,this.seq=0,o&&(this.agentSessionId=o)}async send(t,e){await this.killProcess(),this.runId=g(),this.seq=0;const o=this.agentSessionId??this.sessionId;if(!o){this.emitEvent({state:"error",message:"openclaw: session not started"});return}if(e&&e!==this.currentModelId){const s=await this.runAgentCommand(["--message",`/model ${e}`,"--session-id",o]);if(!s.ok){this.emitEvent({state:"error",message:s.error??"openclaw model switch failed"});return}this.currentModelId=e}this.spawnAgent(["--message",t,"--session-id",o])}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=g(),this.seq=0,this.spawnAgent(["--message","\u200B","--session-id",t])}async stop(){await this.killProcess()}spawnAgent(t){const e=h("openclaw");if(!e){this.emit("error",new Error('Command "openclaw" not found. Is OpenClaw CLI installed?'));return}const o=["agent","--local","--json",...t],s=w(e,o,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:y()});this.process=s;let r="";s.stdout?.on("data",i=>{r+=i.toString()});let c="";s.stderr?.on("data",i=>{c+=i.toString()}),s.on("close",i=>{if(this.process!==s)return;if(this.process=null,i!==0&&i!==null){const f=c.trim()||`openclaw exited with code ${i}`;this.emitEvent({state:"error",message:f});return}const a=r.trim();if(!a){const f=c.trim()||"openclaw returned empty stdout";this.emitEvent({state:"error",message:f});return}let u;try{u=JSON.parse(a)}catch{this.emitEvent({state:"error",message:`openclaw stdout is not valid JSON: ${a.slice(0,200)}`});return}const l=u;if(l.ok===!1||l.success===!1){this.emitEvent({state:"error",message:I(u)??"openclaw request failed"});return}const p=k(u);p&&(this.agentSessionId=p);const m=x(u);m&&this.emitEvent({state:"delta",text:m});const S=C(u);this.emitEvent({state:"final",agentSessionId:this.agentSessionId??void 0,usage:S})}),s.on("error",i=>{this.process===s&&(this.process=null,i.code==="ENOENT"?this.emit("error",new Error('Command "openclaw" not found. Is OpenClaw CLI installed?')):this.emit("error",i))})}async runAgentCommand(t){return new Promise(e=>{const o=h("openclaw");if(!o){e({ok:!1,error:'Command "openclaw" not found. Is OpenClaw CLI installed?'});return}const s=["agent","--local","--json",...t],r=w(o,s,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:y()});let c="",i="";r.stdout?.on("data",a=>{c+=a.toString()}),r.stderr?.on("data",a=>{i+=a.toString()}),r.on("close",a=>{if(a!==0&&a!==null){e({ok:!1,error:i.trim()||`openclaw exited with code ${a}`});return}const u=c.trim();if(!u){e({ok:!0});return}try{const l=JSON.parse(u);if(l.ok===!1||l.success===!1){e({ok:!1,error:I(l)??"openclaw request failed"});return}const d=k(l);d&&(this.agentSessionId=d),e({ok:!0,parsed:l})}catch{e({ok:!1,error:`openclaw stdout is not valid JSON: ${u.slice(0,200)}`})}}),r.on("error",a=>{e({ok:!1,error:a instanceof Error?a.message:String(a)})})})}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}))}}export{j as OpenClawAdapter};
|
|
@@ -1,231 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// @test src/__tests__/opencode-adapter.test.ts
|
|
3
|
-
import { createInterface } from 'node:readline';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
-
import { AgentAdapter, registerAgent } from './adapter.js';
|
|
6
|
-
import { resolveBuiltinCommand, spawnAgentCommand } from './command-spec.js';
|
|
7
|
-
import { buildAgentProcessEnv } from '../agent-env.js';
|
|
8
|
-
function usageFromTokens(tokens) {
|
|
9
|
-
if (!tokens)
|
|
10
|
-
return undefined;
|
|
11
|
-
const input = tokens.input;
|
|
12
|
-
const output = tokens.output;
|
|
13
|
-
if (typeof input !== 'number' && typeof output !== 'number')
|
|
14
|
-
return undefined;
|
|
15
|
-
return {
|
|
16
|
-
inputTokens: typeof input === 'number' ? input : 0,
|
|
17
|
-
outputTokens: typeof output === 'number' ? output : 0,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function stringifyResult(value) {
|
|
21
|
-
if (value == null)
|
|
22
|
-
return '';
|
|
23
|
-
if (typeof value === 'string')
|
|
24
|
-
return value;
|
|
25
|
-
try {
|
|
26
|
-
return JSON.stringify(value);
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return String(value);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function extractErrorMessage(value) {
|
|
33
|
-
if (!value)
|
|
34
|
-
return 'opencode request failed';
|
|
35
|
-
if (typeof value === 'string')
|
|
36
|
-
return value;
|
|
37
|
-
if (typeof value !== 'object')
|
|
38
|
-
return String(value);
|
|
39
|
-
const record = value;
|
|
40
|
-
if (typeof record.message === 'string')
|
|
41
|
-
return record.message;
|
|
42
|
-
if (typeof record.name === 'string')
|
|
43
|
-
return record.name;
|
|
44
|
-
if ('data' in record)
|
|
45
|
-
return extractErrorMessage(record.data);
|
|
46
|
-
return stringifyResult(value) || 'opencode request failed';
|
|
47
|
-
}
|
|
48
|
-
export class OpenCodeAdapter extends AgentAdapter {
|
|
49
|
-
type = 'opencode';
|
|
50
|
-
process = null;
|
|
51
|
-
agentSessionId = null;
|
|
52
|
-
workDir = null;
|
|
53
|
-
seq = 0;
|
|
54
|
-
runId = '';
|
|
55
|
-
terminalState = 'closed';
|
|
56
|
-
async start(_sessionId, workDir, agentSessionId) {
|
|
57
|
-
this.workDir = workDir;
|
|
58
|
-
if (agentSessionId)
|
|
59
|
-
this.agentSessionId = agentSessionId;
|
|
60
|
-
this.resetRunState();
|
|
61
|
-
}
|
|
62
|
-
async send(text, modelId) {
|
|
63
|
-
await this.killProcess();
|
|
64
|
-
this.runId = randomUUID();
|
|
65
|
-
this.resetRunState();
|
|
66
|
-
const args = [
|
|
67
|
-
'run',
|
|
68
|
-
'--format',
|
|
69
|
-
'json',
|
|
70
|
-
'--dangerously-skip-permissions',
|
|
71
|
-
'--dir',
|
|
72
|
-
this.workDir ?? process.cwd(),
|
|
73
|
-
];
|
|
74
|
-
if (this.agentSessionId) {
|
|
75
|
-
args.push('--session', this.agentSessionId);
|
|
76
|
-
}
|
|
77
|
-
if (modelId) {
|
|
78
|
-
args.push('--model', modelId);
|
|
79
|
-
}
|
|
80
|
-
args.push(text);
|
|
81
|
-
this.spawnAndParse(args);
|
|
82
|
-
}
|
|
83
|
-
async resume(agentSessionId) {
|
|
84
|
-
await this.killProcess();
|
|
85
|
-
this.agentSessionId = agentSessionId;
|
|
86
|
-
this.runId = randomUUID();
|
|
87
|
-
this.resetRunState();
|
|
88
|
-
this.emitEvent({ state: 'start', agentSessionId });
|
|
89
|
-
this.emitFinalIfOpen({ state: 'final', agentSessionId });
|
|
90
|
-
}
|
|
91
|
-
async stop() {
|
|
92
|
-
await this.killProcess();
|
|
93
|
-
}
|
|
94
|
-
resetRunState() {
|
|
95
|
-
this.seq = 0;
|
|
96
|
-
this.terminalState = 'open';
|
|
97
|
-
}
|
|
98
|
-
spawnAndParse(args) {
|
|
99
|
-
const spec = resolveBuiltinCommand('opencode');
|
|
100
|
-
if (!spec) {
|
|
101
|
-
this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const proc = spawnAgentCommand(spec, args, {
|
|
105
|
-
cwd: this.workDir ?? undefined,
|
|
106
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
107
|
-
env: buildAgentProcessEnv({ NO_COLOR: '1' }),
|
|
108
|
-
});
|
|
109
|
-
this.process = proc;
|
|
110
|
-
const rl = createInterface({ input: proc.stdout });
|
|
111
|
-
rl.on('line', (line) => {
|
|
112
|
-
if (!line.trim())
|
|
113
|
-
return;
|
|
114
|
-
try {
|
|
115
|
-
const obj = JSON.parse(line);
|
|
116
|
-
this.handleStreamEvent(obj);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
this.emitEvent({ state: 'delta', text: line });
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
let stderrBuf = '';
|
|
123
|
-
proc.stderr?.on('data', (chunk) => {
|
|
124
|
-
stderrBuf += chunk.toString();
|
|
125
|
-
});
|
|
126
|
-
proc.on('close', (code) => {
|
|
127
|
-
if (this.process !== proc)
|
|
128
|
-
return;
|
|
129
|
-
this.process = null;
|
|
130
|
-
if (this.terminalState !== 'open')
|
|
131
|
-
return;
|
|
132
|
-
const stderr = stderrBuf.trim();
|
|
133
|
-
if (code !== 0 && code !== null) {
|
|
134
|
-
this.emitErrorIfOpen({ state: 'error', message: stderr || `opencode exited with code ${code}` });
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
this.emitFinalIfOpen({ state: 'final', agentSessionId: this.agentSessionId ?? undefined });
|
|
138
|
-
});
|
|
139
|
-
proc.on('error', (err) => {
|
|
140
|
-
if (this.process !== proc)
|
|
141
|
-
return;
|
|
142
|
-
this.process = null;
|
|
143
|
-
if (err.code === 'ENOENT') {
|
|
144
|
-
this.emit('error', new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
this.emit('error', err);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
handleStreamEvent(obj) {
|
|
152
|
-
if (obj.sessionID && obj.sessionID !== this.agentSessionId) {
|
|
153
|
-
this.agentSessionId = obj.sessionID;
|
|
154
|
-
this.emitEvent({ state: 'start', agentSessionId: obj.sessionID });
|
|
155
|
-
}
|
|
156
|
-
const part = obj.part;
|
|
157
|
-
if (obj.type === 'text' && part?.text) {
|
|
158
|
-
this.emitEvent({ state: 'delta', text: part.text });
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (obj.type === 'reasoning' && part?.text) {
|
|
162
|
-
this.emitEvent({ state: 'delta', text: part.text, thinking: true });
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (obj.type === 'tool_use' && part?.type === 'tool') {
|
|
166
|
-
if (part.state?.status === 'completed' || part.state?.status === 'error') {
|
|
167
|
-
this.emitEvent({
|
|
168
|
-
state: 'tool-result',
|
|
169
|
-
name: part.tool ?? 'tool',
|
|
170
|
-
result: part.state.status === 'error'
|
|
171
|
-
? part.state.error ?? 'tool failed'
|
|
172
|
-
: stringifyResult(part.state?.output),
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
this.emitEvent({
|
|
177
|
-
state: 'tool-call',
|
|
178
|
-
name: part.tool ?? 'tool',
|
|
179
|
-
args: part.state?.input ?? {},
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (obj.type === 'error') {
|
|
185
|
-
this.emitErrorIfOpen({ state: 'error', message: extractErrorMessage(obj.error) });
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
if (obj.type === 'step_finish') {
|
|
189
|
-
this.emitFinalIfOpen({
|
|
190
|
-
state: 'final',
|
|
191
|
-
agentSessionId: this.agentSessionId ?? obj.sessionID,
|
|
192
|
-
usage: usageFromTokens(part?.tokens),
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
emitEvent(partial) {
|
|
197
|
-
const event = {
|
|
198
|
-
...partial,
|
|
199
|
-
runId: this.runId,
|
|
200
|
-
seq: this.seq++,
|
|
201
|
-
};
|
|
202
|
-
this.emit('agentEvent', event);
|
|
203
|
-
}
|
|
204
|
-
emitFinalIfOpen(partial) {
|
|
205
|
-
if (this.terminalState !== 'open')
|
|
206
|
-
return;
|
|
207
|
-
this.terminalState = 'closed';
|
|
208
|
-
this.emitEvent(partial);
|
|
209
|
-
}
|
|
210
|
-
emitErrorIfOpen(partial) {
|
|
211
|
-
if (this.terminalState !== 'open')
|
|
212
|
-
return;
|
|
213
|
-
this.terminalState = 'closed';
|
|
214
|
-
this.emitEvent(partial);
|
|
215
|
-
}
|
|
216
|
-
async killProcess() {
|
|
217
|
-
const proc = this.process;
|
|
218
|
-
if (!proc)
|
|
219
|
-
return;
|
|
220
|
-
this.process = null;
|
|
221
|
-
proc.kill('SIGTERM');
|
|
222
|
-
await new Promise((resolve) => {
|
|
223
|
-
proc.on('close', resolve);
|
|
224
|
-
setTimeout(() => {
|
|
225
|
-
proc.kill('SIGKILL');
|
|
226
|
-
resolve();
|
|
227
|
-
}, 3000);
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
registerAgent('opencode', () => new OpenCodeAdapter());
|
|
1
|
+
import{createInterface as l}from"node:readline";import{randomUUID as a}from"node:crypto";import{AgentAdapter as d,registerAgent as f}from"./adapter.js";import{resolveBuiltinCommand as m,spawnAgentCommand as h}from"./command-spec.js";import{buildAgentProcessEnv as c}from"../agent-env.js";function g(s){if(!s)return;const t=s.input,e=s.output;if(!(typeof t!="number"&&typeof e!="number"))return{inputTokens:typeof t=="number"?t:0,outputTokens:typeof e=="number"?e:0}}function p(s){if(s==null)return"";if(typeof s=="string")return s;try{return JSON.stringify(s)}catch{return String(s)}}function u(s){if(!s)return"opencode request failed";if(typeof s=="string")return s;if(typeof s!="object")return String(s);const t=s;return typeof t.message=="string"?t.message:typeof t.name=="string"?t.name:"data"in t?u(t.data):p(s)||"opencode request failed"}class I extends d{type="opencode";process=null;agentSessionId=null;workDir=null;seq=0;runId="";terminalState="closed";async start(t,e,n){this.workDir=e,n&&(this.agentSessionId=n),this.resetRunState()}async send(t,e){await this.killProcess(),this.runId=a(),this.resetRunState();const n=["run","--format","json","--dangerously-skip-permissions","--dir",this.workDir??process.cwd()];this.agentSessionId&&n.push("--session",this.agentSessionId),e&&n.push("--model",e),n.push(t),this.spawnAndParse(n)}async resume(t){await this.killProcess(),this.agentSessionId=t,this.runId=a(),this.resetRunState(),this.emitEvent({state:"start",agentSessionId:t}),this.emitFinalIfOpen({state:"final",agentSessionId:t})}async stop(){await this.killProcess()}resetRunState(){this.seq=0,this.terminalState="open"}spawnAndParse(t){const e=m("opencode");if(!e){this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".'));return}const n=h(e,t,{cwd:this.workDir??void 0,stdio:["ignore","pipe","pipe"],env:c({NO_COLOR:"1"})});this.process=n,l({input:n.stdout}).on("line",r=>{if(r.trim())try{const i=JSON.parse(r);this.handleStreamEvent(i)}catch{this.emitEvent({state:"delta",text:r})}});let o="";n.stderr?.on("data",r=>{o+=r.toString()}),n.on("close",r=>{if(this.process!==n||(this.process=null,this.terminalState!=="open"))return;const i=o.trim();if(r!==0&&r!==null){this.emitErrorIfOpen({state:"error",message:i||`opencode exited with code ${r}`});return}this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??void 0})}),n.on("error",r=>{this.process===n&&(this.process=null,r.code==="ENOENT"?this.emit("error",new Error('Command "opencode" not found. Install it with "npm i -g opencode-ai@latest".')):this.emit("error",r))})}handleStreamEvent(t){t.sessionID&&t.sessionID!==this.agentSessionId&&(this.agentSessionId=t.sessionID,this.emitEvent({state:"start",agentSessionId:t.sessionID}));const e=t.part;if(t.type==="text"&&e?.text){this.emitEvent({state:"delta",text:e.text});return}if(t.type==="reasoning"&&e?.text){this.emitEvent({state:"delta",text:e.text,thinking:!0});return}if(t.type==="tool_use"&&e?.type==="tool"){e.state?.status==="completed"||e.state?.status==="error"?this.emitEvent({state:"tool-result",name:e.tool??"tool",result:e.state.status==="error"?e.state.error??"tool failed":p(e.state?.output)}):this.emitEvent({state:"tool-call",name:e.tool??"tool",args:e.state?.input??{}});return}if(t.type==="error"){this.emitErrorIfOpen({state:"error",message:u(t.error)});return}t.type==="step_finish"&&this.emitFinalIfOpen({state:"final",agentSessionId:this.agentSessionId??t.sessionID,usage:g(e?.tokens)})}emitEvent(t){const e={...t,runId:this.runId,seq:this.seq++};this.emit("agentEvent",e)}emitFinalIfOpen(t){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(t))}emitErrorIfOpen(t){this.terminalState==="open"&&(this.terminalState="closed",this.emitEvent(t))}async killProcess(){const t=this.process;t&&(this.process=null,t.kill("SIGTERM"),await new Promise(e=>{t.on("close",e),setTimeout(()=>{t.kill("SIGKILL"),e()},3e3)}))}}f("opencode",()=>new I);export{I as OpenCodeAdapter};
|