@cotal-ai/connector-opencode 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agui-map.d.ts +58 -0
- package/dist/agui-map.d.ts.map +1 -0
- package/dist/agui-source.d.ts +133 -0
- package/dist/agui-source.d.ts.map +1 -0
- package/dist/extension.d.ts.map +1 -1
- package/dist/index.js +115 -64
- package/dist/plugin.bundle.js +2828 -432
- package/dist/plugin.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps OpenCode session records to AG-UI events.
|
|
3
|
+
*
|
|
4
|
+
* **The mapper is a transition machine over per-part state, not a pure function of a record**, for
|
|
5
|
+
* the reason §3.2 gives: the bus re-emits `message.part.updated` for one part id as it streams. It
|
|
6
|
+
* is fed from {@link OpenCodeSessionSource}, which hands over only SETTLED parts, in order, so the
|
|
7
|
+
* transitions here are the ones that survive: a part arrives once, finished.
|
|
8
|
+
*
|
|
9
|
+
* **NO USER-AUTHORED TEXT IS EMITTED, EVER (§3.2, orchestrator ruling).** OpenCode injects a peer
|
|
10
|
+
* batch by prepending it into the human's own text part, so a single part holds peer-authored and
|
|
11
|
+
* human-authored content with no boundary in the record to filter on. The unit of authorship is
|
|
12
|
+
* smaller than the smallest unit the store keeps. Content-parsing the injected header back out is
|
|
13
|
+
* ruled out and is not to be built, prototyped, or left in a comment as an option: it would fail
|
|
14
|
+
* OPEN the moment either formatter changed a character, and a boundary that depends on two
|
|
15
|
+
* formatters agreeing forever is not a boundary. So user messages map to nothing at all.
|
|
16
|
+
*
|
|
17
|
+
* **REMOVAL NEEDS NO HOOK HERE, AND THAT IS A CONSEQUENCE RATHER THAN AN OVERSIGHT.** The ruling on
|
|
18
|
+
* a reverted session is: publish only on finality, drop the per-part state for the vanished ids, log
|
|
19
|
+
* the divergence once, keep going. Because the source hands over only settled parts, this mapper
|
|
20
|
+
* holds no per-part state to drop: a part arrives once, complete, and is mapped in one shot. So the
|
|
21
|
+
* divergence log is the source's, and there is nothing here for a revert to invalidate.
|
|
22
|
+
*/
|
|
23
|
+
import { type AguiEvent, type RecordMapper } from "@cotal-ai/connector-core";
|
|
24
|
+
import type { OpenCodeRecord } from "./agui-source.js";
|
|
25
|
+
export interface OpenCodeMapperOptions {
|
|
26
|
+
/** The native session id. It is the `threadId` on every event and nothing else may claim it. */
|
|
27
|
+
threadId: string;
|
|
28
|
+
/** Mints a `runId`. Connector-minted, so every `RUN_STARTED` carries `runIdSource: "connector"`. */
|
|
29
|
+
mintRunId: () => string;
|
|
30
|
+
/** Emit `REASONING_*` for reasoning parts. Off by default (§7 Q1). */
|
|
31
|
+
reasoning?: boolean;
|
|
32
|
+
/** Arrival clock, for the records carrying no usable source stamp. Injectable for determinism. */
|
|
33
|
+
now?: () => number;
|
|
34
|
+
}
|
|
35
|
+
export interface OpenCodeMapper {
|
|
36
|
+
map: RecordMapper<OpenCodeRecord>;
|
|
37
|
+
/**
|
|
38
|
+
* Close the open run at a boundary the record stream cannot see: `session.idle`, which §3.2 makes
|
|
39
|
+
* the flush boundary. Returns `null` when nothing is open, so calling it twice cannot manufacture
|
|
40
|
+
* a second `RUN_FINISHED` for the bracket machine to refuse.
|
|
41
|
+
*/
|
|
42
|
+
closeOpenRun: (timestamp: number, stopReason?: string) => {
|
|
43
|
+
runId: string;
|
|
44
|
+
events: AguiEvent[];
|
|
45
|
+
} | null;
|
|
46
|
+
/** The run currently open, or `null`. */
|
|
47
|
+
openRun: () => string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Forget a run the emitter closed out of band. KEYED ON THE ID: the report can arrive after this
|
|
50
|
+
* mapper has already opened a newer run, and clearing unconditionally would orphan that one, whose
|
|
51
|
+
* events would then emit under no run at all and halt a session that had done nothing wrong.
|
|
52
|
+
*/
|
|
53
|
+
forgetOpenRun: (runId: string) => void;
|
|
54
|
+
/** Why this session opened no runs, or `null` once one has. A silent refusal is the defect. */
|
|
55
|
+
diagnose: () => string | null;
|
|
56
|
+
}
|
|
57
|
+
export declare function createOpenCodeMapper(opts: OpenCodeMapperOptions): OpenCodeMapper;
|
|
58
|
+
//# sourceMappingURL=agui-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-map.d.ts","sourceRoot":"","sources":["../src/agui-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EACL,KAAK,SAAS,EACd,KAAK,YAAY,EAalB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,MAAM,WAAW,qBAAqB;IACpC,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB,oGAAoG;IACpG,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kGAAkG;IAClG,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;IAClC;;;;OAIG;IACH,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IACxG,yCAAyC;IACzC,OAAO,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,+FAA+F;IAC/F,QAAQ,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC/B;AAoDD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,cAAc,CA4HhF"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link DurableSource} over an OpenCode session, read through the SDK's own
|
|
3
|
+
* `session.messages()` surface rather than the SQLite file behind it.
|
|
4
|
+
*
|
|
5
|
+
* **Why not the database.** `~/.local/share/opencode/opencode.db` is OpenCode's private store: its
|
|
6
|
+
* schema migrates, and reading it would couple this plane to internals no contract covers. The
|
|
7
|
+
* measurements in this file's comments were taken from a copy of that store because it is the only
|
|
8
|
+
* corpus large enough to answer the questions honestly; the SHIPPED read path is the API.
|
|
9
|
+
*
|
|
10
|
+
* **The cursor is the PAIR `messageId:partId`, and the pair is not decoration.** Part ids are
|
|
11
|
+
* monotonic within a message (measured: 0 inversions over 14 915 parts). They are NOT monotonic
|
|
12
|
+
* across a session: ordering every part by (message creation, message id, part id) produces 16
|
|
13
|
+
* inversions over 14 759 ordered pairs, every one of them the first part of a USER message whose id
|
|
14
|
+
* is lower than the last part of the assistant message before it, because the prompt's text part is
|
|
15
|
+
* created while the assistant's final part is still being written. Restricted to the assistant-only
|
|
16
|
+
* stream that §3.2's authorship ruling leaves us with, the count is 0 over 14 084. So a bare
|
|
17
|
+
* `part.id` cursor would be sound only for as long as an unrelated safety ruling keeps user parts
|
|
18
|
+
* out, which is a dependency nobody would remember. The pair is sound either way and costs nothing.
|
|
19
|
+
*
|
|
20
|
+
* **Removal is tolerated by construction.** The cursor is compared as an ORDER, never looked up as
|
|
21
|
+
* an identity, so a session whose parts were reverted away resumes from the same position without
|
|
22
|
+
* the record it names having to still exist. That is what makes the ruling implementable: publish
|
|
23
|
+
* on finality, and treat a later removal as a logged divergence rather than a halt.
|
|
24
|
+
*/
|
|
25
|
+
import type { DurableSource, SourceRead } from "@cotal-ai/connector-core";
|
|
26
|
+
/** The message fields the mapper needs. Deliberately not the whole `Message`: the source hands on
|
|
27
|
+
* identity, authorship and the turn-level completion mark, and nothing it does not read. */
|
|
28
|
+
export interface OpenCodeMessageInfo {
|
|
29
|
+
id: string;
|
|
30
|
+
role: string;
|
|
31
|
+
time?: {
|
|
32
|
+
created?: number;
|
|
33
|
+
completed?: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** One record: a part, plus the message that owns it. The mapper needs both, because authorship and
|
|
37
|
+
* the turn-level finality backstop live on the message, not on the part. */
|
|
38
|
+
export interface OpenCodeRecord {
|
|
39
|
+
part: OpenCodePart;
|
|
40
|
+
message: OpenCodeMessageInfo;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The part shape this source reads. It is a STRUCTURAL subset of the SDK's `Part` union, declared
|
|
44
|
+
* here rather than imported, for the same reason the Claude mapper declares its own entry type: the
|
|
45
|
+
* SDK's type describes what OpenCode may write, and this describes what we actually read. Importing
|
|
46
|
+
* the union would make an SDK bump a compile error in a file that does not care about the fields
|
|
47
|
+
* that changed.
|
|
48
|
+
*/
|
|
49
|
+
export interface OpenCodePart {
|
|
50
|
+
id: string;
|
|
51
|
+
messageID: string;
|
|
52
|
+
type: string;
|
|
53
|
+
text?: string;
|
|
54
|
+
synthetic?: boolean;
|
|
55
|
+
ignored?: boolean;
|
|
56
|
+
callID?: string;
|
|
57
|
+
tool?: string;
|
|
58
|
+
state?: {
|
|
59
|
+
status?: string;
|
|
60
|
+
input?: unknown;
|
|
61
|
+
output?: string;
|
|
62
|
+
error?: string;
|
|
63
|
+
time?: {
|
|
64
|
+
start?: number;
|
|
65
|
+
end?: number;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
time?: {
|
|
69
|
+
start?: number;
|
|
70
|
+
end?: number;
|
|
71
|
+
};
|
|
72
|
+
cost?: number;
|
|
73
|
+
tokens?: unknown;
|
|
74
|
+
}
|
|
75
|
+
/** One entry of `session.messages()`: the message and its parts. */
|
|
76
|
+
export interface OpenCodeMessageWithParts {
|
|
77
|
+
info: OpenCodeMessageInfo;
|
|
78
|
+
parts: OpenCodePart[];
|
|
79
|
+
}
|
|
80
|
+
/** Reads the whole session. Injected so the source can be exercised without a live server. */
|
|
81
|
+
export type ReadSessionMessages = () => Promise<OpenCodeMessageWithParts[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Is this part settled, so that emitting from it now cannot be contradicted later?
|
|
84
|
+
*
|
|
85
|
+
* The question matters because parts are mutated in place long after they appear: 11 518 of 14 915
|
|
86
|
+
* carry `time_updated > time_created`, and 1 657 of the 11 298 that have a successor were updated
|
|
87
|
+
* AFTER their successor already existed, by up to 29.5 minutes. "A later part exists" therefore
|
|
88
|
+
* does NOT mean an earlier one is done, which is the design this measurement killed.
|
|
89
|
+
*
|
|
90
|
+
* The turn-level backstop is what stops a stream from wedging on a part that never gets its own end
|
|
91
|
+
* mark. It is sound in normal operation: the only parts in the corpus updated after their message
|
|
92
|
+
* reported completed are 830 rows rewritten inside a single 150 ms window, ~16 to 20 per
|
|
93
|
+
* millisecond, which is a bulk write rather than session behaviour. It matches no timestamp in the
|
|
94
|
+
* store's migration tables, so it is provably bulk and NOT attributable to a named process.
|
|
95
|
+
*/
|
|
96
|
+
export declare function isSettled(record: OpenCodeRecord): boolean;
|
|
97
|
+
/** Serialise the ordering key. Ids carry no `:`, so the first one separates the halves. */
|
|
98
|
+
export declare function cursorOf(record: OpenCodeRecord): string;
|
|
99
|
+
export interface OpenCodeSessionSourceOptions {
|
|
100
|
+
/** Reads the session. */
|
|
101
|
+
read: ReadSessionMessages;
|
|
102
|
+
/**
|
|
103
|
+
* Called ONCE per read in which the cursor's own record is no longer present, with the cursor
|
|
104
|
+
* that vanished. This is the divergence a revert produces, and it is reported rather than thrown:
|
|
105
|
+
* a user pressing revert is a legitimate session action, and an emitter that died on it would
|
|
106
|
+
* fail the deliverable. Ordering makes the read itself correct without this; the callback exists
|
|
107
|
+
* so the divergence is visible instead of silent.
|
|
108
|
+
*/
|
|
109
|
+
onVanished?: (cursor: string) => void;
|
|
110
|
+
}
|
|
111
|
+
export declare class OpenCodeSessionSource implements DurableSource<OpenCodeRecord> {
|
|
112
|
+
readonly kind = "opencode-session";
|
|
113
|
+
private readonly read0;
|
|
114
|
+
private readonly onVanished?;
|
|
115
|
+
/** The last cursor already reported as vanished. A revert leaves the cursor absent on EVERY read
|
|
116
|
+
* until it advances past the removed region, so reporting per read would turn one divergence
|
|
117
|
+
* into a log flood and bury the next, different one. */
|
|
118
|
+
private reportedVanished?;
|
|
119
|
+
constructor(opts: OpenCodeSessionSourceOptions);
|
|
120
|
+
read(cursor: string | undefined): Promise<SourceRead<OpenCodeRecord>>;
|
|
121
|
+
/**
|
|
122
|
+
* The contiguous run of settled records from the front, and the cursor of the last of them.
|
|
123
|
+
*
|
|
124
|
+
* **STOPPING AT THE FIRST UNSETTLED RECORD IS THE WHOLE POINT**, and it is the same rule
|
|
125
|
+
* `JsonlFileSource` applies to a half-written line: consume up to the last complete unit, leave
|
|
126
|
+
* the rest for the next read. Emitting AROUND a part that is still filling would publish frames
|
|
127
|
+
* out of the order the brackets require, and advancing past it would drop it for good, because
|
|
128
|
+
* nothing ever revisits a cursor. The cost is head-of-line blocking within a session, which is
|
|
129
|
+
* the honest price of ordered brackets rather than an oversight.
|
|
130
|
+
*/
|
|
131
|
+
private settledPrefix;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=agui-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agui-source.d.ts","sourceRoot":"","sources":["../src/agui-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE1E;6FAC6F;AAC7F,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjD;AAED;6EAC6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IACvH,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,oEAAoE;AACpE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,mBAAmB,CAAC;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,8FAA8F;AAC9F,MAAM,MAAM,mBAAmB,GAAG,MAAM,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAAC;AAS5E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAazD;AAED,2FAA2F;AAC3F,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEvD;AAgBD,MAAM,WAAW,4BAA4B;IAC3C,yBAAyB;IACzB,IAAI,EAAE,mBAAmB,CAAC;IAC1B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED,qBAAa,qBAAsB,YAAW,aAAa,CAAC,cAAc,CAAC;IACzE,QAAQ,CAAC,IAAI,sBAAsB;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAA2B;IACvD;;6DAEyD;IACzD,OAAO,CAAC,gBAAgB,CAAC,CAAS;gBAEtB,IAAI,EAAE,4BAA4B;IAKxC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IAmC3E;;;;;;;;;OASG;IACH,OAAO,CAAC,aAAa;CAQtB"}
|
package/dist/extension.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2B,KAAK,SAAS,EAAuE,MAAM,gBAAgB,CAAC;AAuF9I;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2B,KAAK,SAAS,EAAuE,MAAM,gBAAgB,CAAC;AAuF9I;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,EAAE,SA0L/B,CAAC"}
|