@snack-ai/opencode 1.0.0 → 1.0.1
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/package.json +1 -1
- package/src/plugin.js +44 -5
package/package.json
CHANGED
package/src/plugin.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function SnackOpenCodePlugin(_context, options = {}) {
|
|
|
19
19
|
const spoolDirectory = stringOrNull(options.spool_directory);
|
|
20
20
|
const captureFeatures = options.prospective_analysis === true;
|
|
21
21
|
const sourceBindings = bindingMap(options.source_bindings);
|
|
22
|
-
/** @type {Map<string, {promptId: string, provider: string | null, model: string | null, spoolDirectory: string}>} */
|
|
22
|
+
/** @type {Map<string, {promptId: string, provider: string | null, model: string | null, spoolDirectory: string, buffered: Record<string, unknown>[]}>} */
|
|
23
23
|
const prompts = new Map();
|
|
24
24
|
let writes = Promise.resolve();
|
|
25
25
|
let pendingWrites = 0;
|
|
@@ -56,10 +56,44 @@ export async function SnackOpenCodePlugin(_context, options = {}) {
|
|
|
56
56
|
});
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Route a session's spool directory once its provider is known.
|
|
61
|
+
*
|
|
62
|
+
* OpenCode declares `model` optional on `chat.message` and does not send it on `1.18.10`, so the
|
|
63
|
+
* routing decision cannot be taken there. An event written to `_pending` is never attributed and
|
|
64
|
+
* never revisited, so a prompt whose provider is still unknown is held rather than misfiled, and
|
|
65
|
+
* released as soon as `chat.params` names one. A prompt whose provider never arrives is released
|
|
66
|
+
* to `_pending` at the terminal event, which is where it would have gone anyway.
|
|
67
|
+
*
|
|
68
|
+
* @param {{provider: string | null, model: string | null, spoolDirectory: string, buffered: Record<string, unknown>[]}} prompt
|
|
69
|
+
*/
|
|
70
|
+
const release = (prompt) => {
|
|
71
|
+
for (const event of prompt.buffered.splice(0)) {
|
|
72
|
+
append(prompt.spoolDirectory, { ...event, provider: prompt.provider, model: prompt.model });
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
59
76
|
return {
|
|
60
77
|
async dispose() {
|
|
78
|
+
for (const prompt of prompts.values()) release(prompt);
|
|
61
79
|
await writes;
|
|
62
80
|
},
|
|
81
|
+
async "chat.params"(/** @type {Record<string, unknown>} */ input) {
|
|
82
|
+
try {
|
|
83
|
+
const sessionId = stringOrNull(input.sessionID);
|
|
84
|
+
const prompt = sessionId ? prompts.get(sessionId) : undefined;
|
|
85
|
+
if (!prompt || prompt.provider || !spoolDirectory) return;
|
|
86
|
+
const model = recordOrNull(input.model);
|
|
87
|
+
const provider = stringOrNull(model?.providerID);
|
|
88
|
+
if (!provider) return;
|
|
89
|
+
prompt.provider = provider;
|
|
90
|
+
prompt.model = stringOrNull(model?.modelID);
|
|
91
|
+
prompt.spoolDirectory = sourceBindings.get(provider) ?? join(spoolDirectory, "_pending");
|
|
92
|
+
release(prompt);
|
|
93
|
+
} catch {
|
|
94
|
+
// Capture must never change OpenCode prompt behavior.
|
|
95
|
+
}
|
|
96
|
+
},
|
|
63
97
|
async "chat.message"(
|
|
64
98
|
/** @type {Record<string, unknown>} */ input,
|
|
65
99
|
/** @type {unknown} */ output,
|
|
@@ -76,14 +110,16 @@ export async function SnackOpenCodePlugin(_context, options = {}) {
|
|
|
76
110
|
? (sourceBindings.get(provider) ?? join(spoolDirectory, "_pending"))
|
|
77
111
|
: join(spoolDirectory, "_pending");
|
|
78
112
|
if (!targetDirectory) return;
|
|
79
|
-
|
|
113
|
+
const prompt = {
|
|
80
114
|
promptId,
|
|
81
115
|
provider,
|
|
82
116
|
model: modelId,
|
|
83
117
|
spoolDirectory: targetDirectory,
|
|
84
|
-
|
|
118
|
+
/** @type {Record<string, unknown>[]} */ buffered: [],
|
|
119
|
+
};
|
|
120
|
+
prompts.set(sessionId, prompt);
|
|
85
121
|
const occurredAt = new Date().toISOString();
|
|
86
|
-
|
|
122
|
+
const started = {
|
|
87
123
|
schema_version: 1,
|
|
88
124
|
event_id: `chat.message:${sessionId}:${promptId}:${occurredAt}`,
|
|
89
125
|
installation_id: installationId,
|
|
@@ -101,7 +137,9 @@ export async function SnackOpenCodePlugin(_context, options = {}) {
|
|
|
101
137
|
usage_slices: [],
|
|
102
138
|
restrictions: [],
|
|
103
139
|
...(captureFeatures ? { input_features: analyzePrompt(output) } : {}),
|
|
104
|
-
}
|
|
140
|
+
};
|
|
141
|
+
if (provider) append(targetDirectory, started);
|
|
142
|
+
else prompt.buffered.push(started);
|
|
105
143
|
} catch {
|
|
106
144
|
// Capture must never change OpenCode prompt behavior.
|
|
107
145
|
}
|
|
@@ -118,6 +156,7 @@ export async function SnackOpenCodePlugin(_context, options = {}) {
|
|
|
118
156
|
const occurredAt = timestampOrNow(properties.time);
|
|
119
157
|
const error = recordOrNull(properties.error);
|
|
120
158
|
const restricted = type === "session.error" && isExplicitRateLimit(error);
|
|
159
|
+
release(prompt);
|
|
121
160
|
append(prompt.spoolDirectory, {
|
|
122
161
|
schema_version: 1,
|
|
123
162
|
event_id: `${type}:${sessionId}:${prompt.promptId}:${occurredAt}`,
|