@astrosheep/keiyaku 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -5
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +23 -14
- package/build/src/akuma/akuma.js +120 -104
- package/build/src/akuma/archetype.d.ts +7 -6
- package/build/src/akuma/archetype.js +68 -41
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +408 -213
- package/build/src/akuma/heart/facts.d.ts +25 -34
- package/build/src/akuma/heart/facts.js +18 -7
- package/build/src/akuma/heart/index.d.ts +39 -42
- package/build/src/akuma/heart/index.js +134 -93
- package/build/src/akuma/heart/rows.d.ts +9 -31
- package/build/src/akuma/heart/rows.js +26 -44
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +7 -15
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +28 -14
- package/build/src/akuma/heart/storage.js +86 -26
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +6 -1
- package/build/src/akuma/index.js +6 -0
- package/build/src/akuma/projection.d.ts +117 -38
- package/build/src/akuma/projection.js +208 -97
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +12 -4
- package/build/src/akuma/provider.js +117 -70
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/claude/events.js +66 -13
- package/build/src/akuma/providers/claude/index.d.ts +2 -1
- package/build/src/akuma/providers/claude/index.js +35 -21
- package/build/src/akuma/providers/codex-app-server/events.js +4 -1
- package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
- package/build/src/akuma/providers/codex-app-server/index.js +24 -31
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +24 -59
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
- package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
- package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
- package/build/src/akuma/providers/pi/events.js +49 -11
- package/build/src/akuma/providers/pi/index.d.ts +1 -1
- package/build/src/akuma/providers/pi/index.js +50 -21
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -7
- package/build/src/akuma/requests.js +92 -65
- package/build/src/alias/index.d.ts +2 -2
- package/build/src/alias/index.js +10 -10
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/cli/accepted.d.ts +17 -13
- package/build/src/cli/accepted.js +72 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
- package/build/src/cli/commands/akuma-invoke.js +14 -9
- package/build/src/cli/commands/akuma.d.ts +13 -3
- package/build/src/cli/commands/akuma.js +67 -34
- package/build/src/cli/commands/contract.d.ts +18 -5
- package/build/src/cli/commands/contract.js +9 -3
- package/build/src/cli/commands/task-invoke.d.ts +17 -4
- package/build/src/cli/commands/task-invoke.js +43 -20
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +51 -13
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +4 -3
- package/build/src/cli/invoke.js +160 -179
- package/build/src/cli/main.js +34 -6
- package/build/src/cli/parse.d.ts +9 -2
- package/build/src/cli/parse.js +29 -7
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +2 -2
- package/build/src/cli/render/akuma-tool.js +30 -4
- package/build/src/cli/render/akuma.d.ts +1 -0
- package/build/src/cli/render/akuma.js +211 -116
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +192 -79
- package/build/src/cli/render/kanshi.js +263 -168
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -20
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +7 -0
- package/build/src/cli/render/terminal.js +46 -0
- package/build/src/cli/render/text.js +6 -3
- package/build/src/cli/result.d.ts +115 -20
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +6 -5
- package/build/src/contract-worktree.js +95 -57
- package/build/src/coordination/durable-file.d.ts +4 -3
- package/build/src/coordination/durable-file.js +29 -25
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +0 -11
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +1 -7
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +4 -1
- package/build/src/dispatch/index.d.ts +2 -2
- package/build/src/dispatch/index.js +17 -17
- package/build/src/git/admission.d.ts +1 -1
- package/build/src/git/admission.js +11 -11
- package/build/src/git/hooks.js +13 -13
- package/build/src/git/integration.d.ts +14 -15
- package/build/src/git/integration.js +80 -44
- package/build/src/git/observe.d.ts +5 -3
- package/build/src/git/observe.js +18 -12
- package/build/src/git/read-observation.js +9 -9
- package/build/src/git/reconcile.d.ts +2 -0
- package/build/src/git/reconcile.js +138 -95
- package/build/src/git/repository.d.ts +27 -19
- package/build/src/git/repository.js +155 -83
- package/build/src/git/scratch.d.ts +7 -3
- package/build/src/git/scratch.js +44 -37
- package/build/src/git/target-placement.d.ts +40 -4
- package/build/src/git/target-placement.js +221 -101
- package/build/src/git/tender.d.ts +6 -5
- package/build/src/git/tender.js +24 -43
- package/build/src/git/terminal-seal.d.ts +1 -1
- package/build/src/git/terminal-seal.js +7 -5
- package/build/src/git/workspace.d.ts +36 -4
- package/build/src/git/workspace.js +53 -18
- package/build/src/identity/selector.js +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.js +1 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +2 -1
- package/build/src/kanshi/read.js +118 -27
- package/build/src/kanshi/report.d.ts +50 -0
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +32 -0
- package/build/src/library/address.d.ts +12 -3
- package/build/src/library/address.js +77 -46
- package/build/src/library/akuma-creation.d.ts +6 -1
- package/build/src/library/akuma-creation.js +86 -13
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.js +4 -4
- package/build/src/library/catalog.d.ts +1 -1
- package/build/src/library/catalog.js +5 -8
- package/build/src/library/contract.d.ts +5 -6
- package/build/src/library/contract.js +11 -35
- package/build/src/library/delivery.d.ts +2 -1
- package/build/src/library/fleet.d.ts +2 -2
- package/build/src/library/fleet.js +35 -71
- package/build/src/library/keiyaku.d.ts +3 -2
- package/build/src/library/keiyaku.js +1 -0
- package/build/src/library/mutation.d.ts +7 -6
- package/build/src/library/mutation.js +23 -25
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/region.d.ts +12 -0
- package/build/src/library/region.js +15 -1
- package/build/src/library/repo.d.ts +4 -16
- package/build/src/library/repo.js +13 -38
- package/build/src/protocol/attempt.js +6 -6
- package/build/src/protocol/bind.js +4 -4
- package/build/src/protocol/intent.d.ts +15 -2
- package/build/src/protocol/intent.js +21 -6
- package/build/src/protocol/operations.d.ts +73 -22
- package/build/src/protocol/operations.js +292 -88
- package/build/src/protocol/outcome.d.ts +9 -3
- package/build/src/protocol/outcome.js +3 -1
- package/build/src/protocol/placement.d.ts +15 -4
- package/build/src/protocol/placement.js +58 -25
- package/build/src/protocol/read/status.d.ts +8 -1
- package/build/src/protocol/read/status.js +61 -14
- package/build/src/runtime/proc/line-rpc.d.ts +3 -8
- package/build/src/runtime/proc/line-rpc.js +16 -43
- package/build/src/runtime/proc/run.d.ts +15 -29
- package/build/src/runtime/proc/run.js +113 -105
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +2 -2
- package/build/src/settings.js +8 -8
- package/build/src/settlement/fence.d.ts +0 -4
- package/build/src/settlement/fence.js +0 -9
- package/build/src/settlement/holder.d.ts +11 -1
- package/build/src/settlement/holder.js +25 -0
- package/build/src/settlement/settle.d.ts +1 -1
- package/build/src/settlement/settle.js +52 -89
- package/build/src/task/board.d.ts +3 -3
- package/build/src/task/board.js +6 -9
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +14 -14
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +19 -7
- package/build/src/task/index.js +42 -23
- package/build/src/task/operations.d.ts +15 -9
- package/build/src/task/operations.js +78 -48
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +2 -2
- package/build/src/task/store.js +40 -25
- package/build/src/verification/execution.d.ts +4 -2
- package/build/src/verification/execution.js +9 -5
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +13 -2
- package/build/src/world.js +81 -48
- package/package.json +3 -2
- package/build/src/protocol/read/audit.d.ts +0 -31
- package/build/src/protocol/read/audit.js +0 -55
|
@@ -1,31 +1,33 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { abortable } from "../../abort.js";
|
|
1
3
|
import { AgentEventChannel } from "../../provider.js";
|
|
2
4
|
import { createEventState, mapEvent } from "./events.js";
|
|
3
5
|
import { coordinate, loadOpencode, OPENCODE_SDK_PROVIDER, parseModel } from "./session.js";
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
function object(value) {
|
|
7
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
8
|
+
? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
function text(value) {
|
|
11
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
12
|
+
}
|
|
13
|
+
function diagnostic(error) {
|
|
14
|
+
const value = object(error);
|
|
15
|
+
const nested = object(value?.data);
|
|
16
|
+
return text(nested?.message) ?? text(value?.message) ?? String(error);
|
|
17
|
+
}
|
|
18
|
+
function opencodeSessionId(coordinateValue) {
|
|
19
|
+
if (!("sessionId" in coordinateValue) || coordinateValue.sessionId === undefined) {
|
|
7
20
|
throw new Error("OpenCode resume requires sessionId");
|
|
8
21
|
}
|
|
9
|
-
return
|
|
22
|
+
return coordinateValue.sessionId;
|
|
10
23
|
}
|
|
11
24
|
function admit(options) {
|
|
12
|
-
if (options.access !== undefined)
|
|
13
|
-
throw new Error("OpenCode does not support explicit access");
|
|
14
25
|
if (options.network !== undefined)
|
|
15
26
|
throw new Error("OpenCode does not support explicit network");
|
|
16
|
-
if (options.systemPrompt !== undefined && options.systemPrompt.length > 0) {
|
|
17
|
-
throw new Error("OpenCode V2 does not expose an enforceable system prompt option");
|
|
18
|
-
}
|
|
19
|
-
if (options.effort !== undefined && options.model === undefined) {
|
|
20
|
-
throw new Error("OpenCode effort requires an explicit model");
|
|
21
|
-
}
|
|
22
27
|
if (options.model !== undefined)
|
|
23
|
-
parseModel(options.model
|
|
28
|
+
parseModel(options.model);
|
|
24
29
|
}
|
|
25
30
|
function eventValue(value) {
|
|
26
|
-
const envelope = objectData(value);
|
|
27
|
-
if (envelope !== undefined && typeof envelope.data === "string")
|
|
28
|
-
return eventValue(envelope.data);
|
|
29
31
|
if (typeof value !== "string")
|
|
30
32
|
return value;
|
|
31
33
|
try {
|
|
@@ -35,135 +37,228 @@ function eventValue(value) {
|
|
|
35
37
|
return { type: value };
|
|
36
38
|
}
|
|
37
39
|
}
|
|
40
|
+
function promptBody(input, messageID) {
|
|
41
|
+
const model = input.options.model === undefined ? undefined : parseModel(input.options.model);
|
|
42
|
+
const promptText = [input.body, ...input.launchTells.map((tell) => tell.text)]
|
|
43
|
+
.filter((part) => part.length > 0)
|
|
44
|
+
.join("\n\n");
|
|
45
|
+
return {
|
|
46
|
+
messageID,
|
|
47
|
+
...(model === undefined ? {} : { model }),
|
|
48
|
+
...(input.options.effort === undefined ? {} : { variant: input.options.effort }),
|
|
49
|
+
...(input.options.systemPrompt === undefined ? {} : { system: input.options.systemPrompt }),
|
|
50
|
+
parts: [{ type: "text", text: promptText }],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function messageRows(value, sessionId) {
|
|
54
|
+
const response = object(value);
|
|
55
|
+
const rows = Array.isArray(response?.data) ? response.data : Array.isArray(value) ? value : [];
|
|
56
|
+
return rows.flatMap((row) => {
|
|
57
|
+
const entry = object(row);
|
|
58
|
+
const info = object(entry?.info);
|
|
59
|
+
if (info?.sessionID !== sessionId)
|
|
60
|
+
return [];
|
|
61
|
+
return [{ info, parts: Array.isArray(entry?.parts) ? entry.parts : [] }];
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function newest(rows) {
|
|
65
|
+
return rows.reduce((latest, row) => {
|
|
66
|
+
if (latest === undefined)
|
|
67
|
+
return row;
|
|
68
|
+
const at = Number(object(row.info.time)?.created ?? 0);
|
|
69
|
+
const latestAt = Number(object(latest.info.time)?.created ?? 0);
|
|
70
|
+
return at >= latestAt ? row : latest;
|
|
71
|
+
}, undefined);
|
|
72
|
+
}
|
|
73
|
+
async function readTurnResult(input) {
|
|
74
|
+
try {
|
|
75
|
+
const response = await input.session.messages({
|
|
76
|
+
path: { id: input.sessionId },
|
|
77
|
+
query: { directory: input.cwd },
|
|
78
|
+
throwOnError: true,
|
|
79
|
+
});
|
|
80
|
+
const rows = messageRows(response, input.sessionId);
|
|
81
|
+
const user = rows.find((row) => row.info.role === "user" && row.info.id === input.messageID);
|
|
82
|
+
if (user === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
const userId = text(user?.info.id);
|
|
85
|
+
const assistant = newest(rows.filter((row) => row.info.role === "assistant" && row.info.parentID === userId));
|
|
86
|
+
const historyId = text(assistant?.info.id);
|
|
87
|
+
if (input.state.failure !== undefined)
|
|
88
|
+
return { kind: "failed", diagnostic: input.state.failure };
|
|
89
|
+
if (assistant === undefined)
|
|
90
|
+
return { kind: "failed", diagnostic: "OpenCode completed without a native assistant answer" };
|
|
91
|
+
if (assistant.info.error !== undefined)
|
|
92
|
+
return { kind: "failed", diagnostic: diagnostic(assistant.info.error) };
|
|
93
|
+
const answer = assistant.parts
|
|
94
|
+
.map((part) => object(part))
|
|
95
|
+
.filter((part) => part?.type === "text")
|
|
96
|
+
.map((part) => part?.text)
|
|
97
|
+
.filter((part) => typeof part === "string")
|
|
98
|
+
.join("\n\n");
|
|
99
|
+
return historyId === undefined ? { kind: "answered", answer } : { kind: "answered", answer, historyId };
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return { kind: "failed", diagnostic: diagnostic(error) };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function nativeSessionId(value) {
|
|
106
|
+
const event = object(value);
|
|
107
|
+
const properties = object(event?.properties);
|
|
108
|
+
const direct = text(properties?.sessionID);
|
|
109
|
+
if (direct !== undefined)
|
|
110
|
+
return direct;
|
|
111
|
+
const info = object(properties?.info);
|
|
112
|
+
const part = object(properties?.part);
|
|
113
|
+
return text(info?.sessionID) ?? text(part?.sessionID);
|
|
114
|
+
}
|
|
115
|
+
function scopedProperties(value, sessionId) {
|
|
116
|
+
const properties = object(object(value)?.properties);
|
|
117
|
+
return nativeSessionId(value) === sessionId ? properties : undefined;
|
|
118
|
+
}
|
|
119
|
+
function stopIterator(iterator) {
|
|
120
|
+
const stopped = iterator?.return?.(undefined);
|
|
121
|
+
void stopped?.catch(() => undefined);
|
|
122
|
+
}
|
|
123
|
+
function nativeProgress(type, properties, busy) {
|
|
124
|
+
if (type === "session.idle")
|
|
125
|
+
return { busy, terminal: busy };
|
|
126
|
+
if (type !== "session.status")
|
|
127
|
+
return { busy, terminal: false };
|
|
128
|
+
const status = object(properties.status)?.type;
|
|
129
|
+
const nextBusy = busy || status === "busy";
|
|
130
|
+
return { busy: nextBusy, terminal: status === "idle" && nextBusy };
|
|
131
|
+
}
|
|
132
|
+
function startsTurn(value, sessionId, messageID) {
|
|
133
|
+
const event = object(value);
|
|
134
|
+
const info = object(object(event?.properties)?.info);
|
|
135
|
+
return event?.type === "message.updated"
|
|
136
|
+
&& info?.sessionID === sessionId
|
|
137
|
+
&& info.role === "user"
|
|
138
|
+
&& info.id === messageID;
|
|
139
|
+
}
|
|
140
|
+
async function observeTurn(input) {
|
|
141
|
+
let active = false;
|
|
142
|
+
let busy = false;
|
|
143
|
+
try {
|
|
144
|
+
for (;;) {
|
|
145
|
+
const next = await input.iterator.next();
|
|
146
|
+
if (next.done)
|
|
147
|
+
return { kind: "failed", diagnostic: "OpenCode event stream ended before Turn completion" };
|
|
148
|
+
const value = eventValue(next.value);
|
|
149
|
+
const observedSessionId = nativeSessionId(value);
|
|
150
|
+
if (observedSessionId !== undefined && observedSessionId !== input.sessionId)
|
|
151
|
+
continue;
|
|
152
|
+
const event = object(value);
|
|
153
|
+
const properties = scopedProperties(value, input.sessionId);
|
|
154
|
+
if (properties === undefined) {
|
|
155
|
+
if (active)
|
|
156
|
+
mapEvent(value, input.events, input.state);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (startsTurn(value, input.sessionId, input.messageID)) {
|
|
160
|
+
active = true;
|
|
161
|
+
busy = false;
|
|
162
|
+
}
|
|
163
|
+
const submittedError = event?.type === "session.error" && input.submissionState.started;
|
|
164
|
+
if (!active && !submittedError)
|
|
165
|
+
continue;
|
|
166
|
+
mapEvent(value, input.events, input.state);
|
|
167
|
+
if (event?.type === "session.error") {
|
|
168
|
+
return { kind: "failed", diagnostic: input.state.failure ?? diagnostic(properties.error) };
|
|
169
|
+
}
|
|
170
|
+
const progress = nativeProgress(event?.type, properties, busy);
|
|
171
|
+
busy = progress.busy;
|
|
172
|
+
if (!progress.terminal)
|
|
173
|
+
continue;
|
|
174
|
+
const result = await readTurnResult(input);
|
|
175
|
+
if (result !== undefined)
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return { kind: "failed", diagnostic: `OpenCode event stream failed: ${diagnostic(error)}` };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function terminalSettlement(events, close) {
|
|
184
|
+
let finish;
|
|
185
|
+
const completion = new Promise((resolve) => {
|
|
186
|
+
let settlement;
|
|
187
|
+
finish = (result) => {
|
|
188
|
+
settlement ??= (async () => {
|
|
189
|
+
events.end();
|
|
190
|
+
await close();
|
|
191
|
+
resolve(result);
|
|
192
|
+
})();
|
|
193
|
+
return settlement;
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
return { completion, finish };
|
|
197
|
+
}
|
|
38
198
|
async function drive(execution, input, loader) {
|
|
39
199
|
admit(input.options);
|
|
200
|
+
const signal = input.signal ?? new AbortController().signal;
|
|
40
201
|
const resumeSessionId = input.session.kind === "resume"
|
|
41
202
|
? opencodeSessionId(input.session.coordinate)
|
|
42
203
|
: undefined;
|
|
43
|
-
const events = new AgentEventChannel();
|
|
44
|
-
const state = createEventState();
|
|
45
204
|
const abortController = new AbortController();
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
205
|
+
const abortSetup = () => abortController.abort(signal.reason);
|
|
206
|
+
signal.addEventListener("abort", abortSetup, { once: true });
|
|
207
|
+
signal.throwIfAborted();
|
|
208
|
+
const runtime = await abortable(loadOpencode(execution, input.cwd, abortController.signal, loader), abortController.signal, async (late) => await late.close());
|
|
209
|
+
let closing;
|
|
210
|
+
let iterator;
|
|
211
|
+
const closeOnce = () => {
|
|
212
|
+
closing ??= runtime.close();
|
|
213
|
+
return closing;
|
|
53
214
|
};
|
|
54
215
|
try {
|
|
55
|
-
const session = runtime.client.
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
const info = objectData(responseData?.data) ?? responseData;
|
|
62
|
-
const sessionId = textData(info?.id) ?? resumeSessionId;
|
|
216
|
+
const session = runtime.client.session;
|
|
217
|
+
const sessionResponse = await abortable(input.session.kind === "fresh"
|
|
218
|
+
? session.create({ query: { directory: input.cwd }, throwOnError: true })
|
|
219
|
+
: session.get({ path: { id: resumeSessionId }, query: { directory: input.cwd }, throwOnError: true }), abortController.signal);
|
|
220
|
+
const info = object(object(sessionResponse)?.data) ?? object(sessionResponse);
|
|
221
|
+
const sessionId = text(info?.id) ?? resumeSessionId;
|
|
63
222
|
if (sessionId === undefined)
|
|
64
223
|
throw new Error("OpenCode did not return a session id");
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
224
|
+
const events = new AgentEventChannel();
|
|
225
|
+
const state = createEventState(sessionId);
|
|
226
|
+
const messageID = `msg_${randomUUID().replaceAll("-", "")}`;
|
|
227
|
+
const submissionState = { started: false };
|
|
68
228
|
events.emit({ type: "session", coordinate: coordinate(sessionId) });
|
|
69
|
-
const streamResult = await
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
settle(result);
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
settle({ kind: "failed", diagnostic: `OpenCode cleanup failed: ${diagnostic(error)}` });
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
void (async () => {
|
|
91
|
-
try {
|
|
92
|
-
await consumeSession(session, sessionId, iterator, state, events);
|
|
93
|
-
const messages = await session.messages({ sessionID: sessionId, order: "desc", limit: 32 }, { throwOnError: true });
|
|
94
|
-
const final = messages.data.data.find((message) => message.type === "assistant");
|
|
95
|
-
const answer = final?.type === "assistant" ? final.content.filter((part) => part.type === "text").map((part) => part.text).join("\n\n") : state.answer.join("\n\n");
|
|
96
|
-
const historyId = final?.type === "assistant" ? textData(final.id) : undefined;
|
|
97
|
-
if (state.failure !== undefined || final?.type === "assistant" && final.error !== undefined)
|
|
98
|
-
await finish({ kind: "failed", diagnostic: state.failure ?? diagnostic(final?.type === "assistant" ? final.error : undefined) });
|
|
99
|
-
else if (historyId === undefined) {
|
|
100
|
-
await finish({ kind: "failed", diagnostic: "OpenCode completed without a native assistant answer/history point" });
|
|
101
|
-
}
|
|
102
|
-
else
|
|
103
|
-
await finish({ kind: "answered", answer, historyId });
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
await finish({ kind: "failed", diagnostic: diagnostic(error) });
|
|
107
|
-
}
|
|
108
|
-
})();
|
|
229
|
+
const streamResult = await abortable(runtime.client.event.subscribe({ query: { directory: input.cwd } }), abortController.signal, async (late) => { await late.stream[Symbol.asyncIterator]().return?.(undefined); });
|
|
230
|
+
iterator = streamResult.stream[Symbol.asyncIterator]();
|
|
231
|
+
const { completion, finish } = terminalSettlement(events, closeOnce);
|
|
232
|
+
const observation = observeTurn({
|
|
233
|
+
iterator, session, sessionId, cwd: input.cwd, messageID, events, state, submissionState,
|
|
234
|
+
});
|
|
235
|
+
submissionState.started = true;
|
|
236
|
+
await abortable(session.promptAsync({
|
|
237
|
+
path: { id: sessionId },
|
|
238
|
+
query: { directory: input.cwd },
|
|
239
|
+
body: promptBody(input, messageID),
|
|
240
|
+
throwOnError: true,
|
|
241
|
+
}), abortController.signal);
|
|
242
|
+
void observation.then(finish);
|
|
243
|
+
signal.removeEventListener("abort", abortSetup);
|
|
109
244
|
return {
|
|
110
|
-
admission: { fence },
|
|
245
|
+
admission: { fence: sessionId },
|
|
111
246
|
events,
|
|
112
247
|
completion,
|
|
113
248
|
abort: async () => {
|
|
114
249
|
abortController.abort();
|
|
115
|
-
void session.
|
|
250
|
+
void session.abort({ path: { id: sessionId }, query: { directory: input.cwd }, throwOnError: true }).catch(() => undefined);
|
|
116
251
|
await finish({ kind: "failed", diagnostic: "OpenCode session interrupted" });
|
|
117
252
|
},
|
|
118
253
|
};
|
|
119
254
|
}
|
|
120
255
|
catch (error) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
catch (cleanupError) {
|
|
125
|
-
throw new Error(`OpenCode setup failed: ${diagnostic(error)}; cleanup failed: ${diagnostic(cleanupError)}`);
|
|
126
|
-
}
|
|
256
|
+
signal.removeEventListener("abort", abortSetup);
|
|
257
|
+
stopIterator(iterator);
|
|
258
|
+
await closeOnce();
|
|
127
259
|
throw error;
|
|
128
260
|
}
|
|
129
261
|
}
|
|
130
|
-
function objectData(value) { return value !== null && typeof value === "object" ? value : undefined; }
|
|
131
|
-
function textData(value) { return typeof value === "string" && value.trim().length > 0 ? value : undefined; }
|
|
132
|
-
function admittedFence(sessionId, response) {
|
|
133
|
-
const outer = objectData(response);
|
|
134
|
-
const envelope = objectData(outer?.data);
|
|
135
|
-
const admission = objectData(envelope?.data) ?? envelope;
|
|
136
|
-
const id = textData(admission?.id);
|
|
137
|
-
const sequence = typeof admission?.admittedSeq === "number" && Number.isSafeInteger(admission.admittedSeq)
|
|
138
|
-
? admission.admittedSeq : undefined;
|
|
139
|
-
if (id === undefined && sequence === undefined)
|
|
140
|
-
throw new Error("OpenCode did not return prompt admission evidence");
|
|
141
|
-
return `${sessionId}:${sequence ?? id}`;
|
|
142
|
-
}
|
|
143
|
-
async function consumeSession(session, sessionId, iterator, state, events) {
|
|
144
|
-
const stream = (async () => {
|
|
145
|
-
for (;;) {
|
|
146
|
-
const next = await iterator.next();
|
|
147
|
-
if (next.done)
|
|
148
|
-
return;
|
|
149
|
-
mapEvent(eventValue(next.value), events, state);
|
|
150
|
-
}
|
|
151
|
-
})();
|
|
152
|
-
stream.catch(() => undefined);
|
|
153
|
-
await session.wait({ sessionID: sessionId }, { throwOnError: true });
|
|
154
|
-
await iterator.return?.();
|
|
155
|
-
await stream;
|
|
156
|
-
let after;
|
|
157
|
-
for (;;) {
|
|
158
|
-
const history = await session.history({ sessionID: sessionId, limit: 200, ...(after === undefined ? {} : { after }) }, { throwOnError: true });
|
|
159
|
-
for (const event of history.data.data)
|
|
160
|
-
mapEvent(event, events, state);
|
|
161
|
-
const latest = history.data.data.at(-1)?.durable?.seq;
|
|
162
|
-
if (!history.data.hasMore || latest === undefined || latest === after)
|
|
163
|
-
return;
|
|
164
|
-
after = latest;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
262
|
export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, kind: "opencode-sdk" }) {
|
|
168
263
|
const execution = "kind" in input ? input : { name: OPENCODE_SDK_PROVIDER, kind: "opencode-sdk" };
|
|
169
264
|
const loader = "loader" in input ? input.loader : undefined;
|
|
@@ -176,7 +271,16 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
|
|
|
176
271
|
catch (error) {
|
|
177
272
|
return { kind: "refused", diagnostic: diagnostic(error) };
|
|
178
273
|
}
|
|
179
|
-
return {
|
|
274
|
+
return {
|
|
275
|
+
kind: "admitted",
|
|
276
|
+
options: Object.freeze({ ...options }),
|
|
277
|
+
...(options.readonly === undefined ? {} : {
|
|
278
|
+
readonly: {
|
|
279
|
+
enforcement: "none",
|
|
280
|
+
diagnostic: "OpenCode V1 cannot remove task-surface mutation capabilities",
|
|
281
|
+
},
|
|
282
|
+
}),
|
|
283
|
+
};
|
|
180
284
|
},
|
|
181
285
|
start: (input) => drive(execution, input, loader),
|
|
182
286
|
resume: (input) => drive(execution, input, loader),
|
|
@@ -184,12 +288,13 @@ export function createOpencodeProvider(input = { name: OPENCODE_SDK_PROVIDER, ki
|
|
|
184
288
|
const sessionId = opencodeSessionId(input.session);
|
|
185
289
|
const runtime = await loadOpencode(execution, input.cwd, new AbortController().signal, loader);
|
|
186
290
|
try {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
291
|
+
const result = await runtime.client.session.fork({
|
|
292
|
+
path: { id: sessionId },
|
|
293
|
+
query: { directory: input.cwd },
|
|
294
|
+
body: { messageID: input.at },
|
|
295
|
+
});
|
|
296
|
+
const info = object(object(result)?.data) ?? object(result);
|
|
297
|
+
const id = text(info?.id);
|
|
193
298
|
if (id === undefined || id === sessionId)
|
|
194
299
|
throw new Error("OpenCode fork returned an invalid session id");
|
|
195
300
|
return { session: coordinate(id) };
|
|
@@ -1,39 +1,24 @@
|
|
|
1
|
-
import { type OpencodeClient } from "@opencode-ai/sdk
|
|
2
|
-
import
|
|
3
|
-
|
|
1
|
+
import { type OpencodeClient } from "@opencode-ai/sdk";
|
|
2
|
+
import type { ResumeCoordinate } from "../../heart/index.js";
|
|
3
|
+
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
4
|
+
export type OpencodeSdkSession = Pick<OpencodeClient["session"], "create" | "get" | "fork" | "abort" | "promptAsync" | "messages">;
|
|
5
|
+
export type OpencodeSdkEvent = Pick<OpencodeClient["event"], "subscribe">;
|
|
4
6
|
export type OpencodeSdkLoader = (cwd: string) => Promise<Readonly<{
|
|
5
7
|
client: {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
model?: Pick<OpencodeClient["v2"]["model"], "list">;
|
|
9
|
-
};
|
|
10
|
-
session?: {
|
|
11
|
-
fork(params: {
|
|
12
|
-
sessionID: string;
|
|
13
|
-
messageID: string;
|
|
14
|
-
}): Promise<unknown>;
|
|
15
|
-
};
|
|
8
|
+
session: OpencodeSdkSession;
|
|
9
|
+
event: OpencodeSdkEvent;
|
|
16
10
|
};
|
|
17
11
|
close?: () => Promise<void> | void;
|
|
18
12
|
}>>;
|
|
19
13
|
export declare const OPENCODE_SDK_PROVIDER: "opencode-sdk";
|
|
20
|
-
export declare function parseModel(model: string
|
|
14
|
+
export declare function parseModel(model: string): Readonly<{
|
|
21
15
|
providerID: string;
|
|
22
|
-
|
|
23
|
-
variant?: string;
|
|
16
|
+
modelID: string;
|
|
24
17
|
}>;
|
|
25
18
|
export type OpencodeRuntime = Readonly<{
|
|
26
19
|
client: {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
model?: Pick<OpencodeClient["v2"]["model"], "list">;
|
|
30
|
-
};
|
|
31
|
-
session?: {
|
|
32
|
-
fork(params: {
|
|
33
|
-
sessionID: string;
|
|
34
|
-
messageID: string;
|
|
35
|
-
}): Promise<unknown>;
|
|
36
|
-
};
|
|
20
|
+
session: OpencodeSdkSession;
|
|
21
|
+
event: OpencodeSdkEvent;
|
|
37
22
|
};
|
|
38
23
|
close: () => Promise<void>;
|
|
39
24
|
}>;
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { abortable } from "../../abort.js";
|
|
3
|
+
import { spawnDetachedProcess } from "../../../runtime/proc/run.js";
|
|
4
|
+
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
4
5
|
export const OPENCODE_SDK_PROVIDER = "opencode-sdk";
|
|
5
|
-
export function parseModel(model
|
|
6
|
+
export function parseModel(model) {
|
|
6
7
|
const slash = model.indexOf("/");
|
|
7
8
|
if (slash <= 0 || slash === model.length - 1)
|
|
8
9
|
throw new Error("OpenCode model must be <provider>/<model>");
|
|
9
|
-
return { providerID: model.slice(0, slash),
|
|
10
|
+
return { providerID: model.slice(0, slash), modelID: model.slice(slash + 1) };
|
|
10
11
|
}
|
|
11
12
|
async function waitReady(client, cwd, signal) {
|
|
12
13
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
13
14
|
if (signal.aborted)
|
|
14
15
|
throw new Error("OpenCode startup aborted");
|
|
15
16
|
try {
|
|
16
|
-
await client.
|
|
17
|
+
await abortable(client.session.list({ query: { directory: cwd }, throwOnError: true }), signal);
|
|
17
18
|
return;
|
|
18
19
|
}
|
|
19
20
|
catch {
|
|
@@ -43,10 +44,10 @@ export async function loadOpencode(execution, cwd, signal, loader) {
|
|
|
43
44
|
return { client: loaded.client, close: async () => { await loaded.close?.(); } };
|
|
44
45
|
}
|
|
45
46
|
const port = await availablePort();
|
|
46
|
-
const
|
|
47
|
+
const owned = await spawnDetachedProcess({
|
|
47
48
|
argv: [execution.executable ?? "opencode", "serve", "--hostname", "127.0.0.1", "--port", String(port)],
|
|
48
49
|
cwd,
|
|
49
|
-
env: { ...process.env, ...
|
|
50
|
+
env: { ...process.env, ...execution.env },
|
|
50
51
|
log: `${cwd}/.opencode.log`,
|
|
51
52
|
});
|
|
52
53
|
const client = createOpencodeClient({ baseUrl: `http://127.0.0.1:${port}`, directory: cwd });
|
|
@@ -54,9 +55,9 @@ export async function loadOpencode(execution, cwd, signal, loader) {
|
|
|
54
55
|
await waitReady(client, cwd, signal);
|
|
55
56
|
}
|
|
56
57
|
catch (error) {
|
|
57
|
-
await
|
|
58
|
+
await owned.terminate();
|
|
58
59
|
throw error;
|
|
59
60
|
}
|
|
60
|
-
return { client, close: async () => { await
|
|
61
|
+
return { client, close: async () => { await owned.terminate(); } };
|
|
61
62
|
}
|
|
62
63
|
export function coordinate(sessionId) { return { sessionId }; }
|
|
@@ -42,19 +42,57 @@ function hasTextContent(content) {
|
|
|
42
42
|
return value?.type === "text" && typeof value.text === "string";
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
+
function positiveLine(value) {
|
|
46
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1 ? value : undefined;
|
|
47
|
+
}
|
|
48
|
+
function optionalText(value) {
|
|
49
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
50
|
+
}
|
|
51
|
+
function runCall(name, value) {
|
|
52
|
+
return name === "bash" && typeof value.command === "string"
|
|
53
|
+
? { kind: "run", command: value.command }
|
|
54
|
+
: undefined;
|
|
55
|
+
}
|
|
56
|
+
function readCall(name, value) {
|
|
57
|
+
if (name !== "read" || typeof value.path !== "string")
|
|
58
|
+
return undefined;
|
|
59
|
+
const offset = positiveLine(value.offset);
|
|
60
|
+
const limit = positiveLine(value.limit);
|
|
61
|
+
return {
|
|
62
|
+
kind: "read",
|
|
63
|
+
path: value.path,
|
|
64
|
+
...(offset === undefined ? {} : { offset }),
|
|
65
|
+
...(limit === undefined ? {} : { limit }),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function searchCall(name, value) {
|
|
69
|
+
if (name !== "grep" && name !== "find")
|
|
70
|
+
return undefined;
|
|
71
|
+
const query = value.pattern ?? value.query;
|
|
72
|
+
if (typeof query !== "string")
|
|
73
|
+
return undefined;
|
|
74
|
+
const path = optionalText(value.path);
|
|
75
|
+
const glob = optionalText(value.glob);
|
|
76
|
+
return {
|
|
77
|
+
kind: "search",
|
|
78
|
+
query,
|
|
79
|
+
scope: name === "find" ? "files" : "content",
|
|
80
|
+
...(path === undefined ? {} : { path }),
|
|
81
|
+
...(name === "grep" && glob !== undefined ? { glob } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function fileChangeCall(name, value) {
|
|
85
|
+
if ((name !== "edit" && name !== "write") || typeof value.path !== "string")
|
|
86
|
+
return undefined;
|
|
87
|
+
return { kind: "fileChange", changes: [{ op: name === "write" ? "add" : "update", path: value.path }] };
|
|
88
|
+
}
|
|
45
89
|
function toolCall(name, args) {
|
|
46
90
|
const value = record(args) ?? {};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { kind: "search", query: (value.pattern ?? value.query) };
|
|
53
|
-
}
|
|
54
|
-
if ((name === "edit" || name === "write") && typeof value.path === "string") {
|
|
55
|
-
return { kind: "fileChange", changes: [{ op: name === "write" ? "add" : "update", path: value.path }] };
|
|
56
|
-
}
|
|
57
|
-
return { kind: "other", display: name };
|
|
91
|
+
return runCall(name, value)
|
|
92
|
+
?? readCall(name, value)
|
|
93
|
+
?? searchCall(name, value)
|
|
94
|
+
?? fileChangeCall(name, value)
|
|
95
|
+
?? { kind: "other", display: name };
|
|
58
96
|
}
|
|
59
97
|
function translateNote(event) {
|
|
60
98
|
if (event.type === "auto_retry_start")
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAgentSession, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, type CreateAgentSessionOptions } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { ProviderExecution } from "../../
|
|
2
|
+
import type { ProviderExecution } from "../../provider-recipe.js";
|
|
3
3
|
import { type ProviderAdapter } from "../../provider.js";
|
|
4
4
|
export type PiSdk = Readonly<{
|
|
5
5
|
createAgentSession(options?: CreateAgentSessionOptions): ReturnType<typeof createAgentSession>;
|