@digipair/skill-opencode 0.141.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/README.md +11 -0
- package/dist/index.cjs.js +143 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +44712 -0
- package/dist/schema.fr.json +132 -0
- package/dist/schema.json +132 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/lib/skill-opencode.d.ts +3 -0
- package/dist/src/lib/skill-opencode.d.ts.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# skill-opencode
|
|
2
|
+
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build skill-opencode` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test skill-opencode` to execute the unit tests via [Jest](https://jestjs.io).
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var engine = require('@digipair/engine');
|
|
4
|
+
var child_process = require('child_process');
|
|
5
|
+
var fs = require('fs');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
|
|
8
|
+
let OpencodeService = class OpencodeService {
|
|
9
|
+
/**
|
|
10
|
+
* Runs the local opencode CLI with the provided prompt and returns the assistant response.
|
|
11
|
+
* The opencode binary is a native executable shipped by the `opencode-ai` package
|
|
12
|
+
* (bin/opencode.exe) and is invoked directly: `opencode run --format json "<prompt>"`.
|
|
13
|
+
*
|
|
14
|
+
* With `format: 'json'` opencode streams newline-delimited JSON events (JSONL). Each event is
|
|
15
|
+
* parsed and forwarded to `onAction`, the `text` parts are accumulated and returned as the result.
|
|
16
|
+
* With `format: 'default'` the raw trimmed stdout is returned instead.
|
|
17
|
+
*/ async runPrompt(params, _pinsSettingsList, context) {
|
|
18
|
+
const { prompt, agent, model = context.privates?.OPENCODE_MODEL, format = 'json', cwd = process.cwd() + '/factory/digipairs', timeoutMs, onStdout = [], onAction = [], debug = false } = params;
|
|
19
|
+
if (!prompt || !prompt.trim()) {
|
|
20
|
+
throw new Error('Prompt must be a non-empty string');
|
|
21
|
+
}
|
|
22
|
+
// The opencode-ai package ships a native binary in bin/opencode.exe.
|
|
23
|
+
const opencodeBin = path.join(path.dirname(require.resolve('opencode-ai/package.json')), 'bin', 'opencode.exe');
|
|
24
|
+
if (!fs.existsSync(opencodeBin)) {
|
|
25
|
+
throw new Error(`opencode CLI not found. Ensure opencode-ai is installed. Looked for: ${opencodeBin}`);
|
|
26
|
+
}
|
|
27
|
+
// Non-interactive automation mode: `opencode run "<prompt>"`.
|
|
28
|
+
const args = [
|
|
29
|
+
'run'
|
|
30
|
+
];
|
|
31
|
+
if (model) args.push('--model', model);
|
|
32
|
+
if (agent) args.push('--agent', agent);
|
|
33
|
+
args.push('--format', format);
|
|
34
|
+
args.push(prompt);
|
|
35
|
+
const child = child_process.spawn(opencodeBin, args, {
|
|
36
|
+
cwd,
|
|
37
|
+
env: {
|
|
38
|
+
...process.env
|
|
39
|
+
},
|
|
40
|
+
stdio: [
|
|
41
|
+
'ignore',
|
|
42
|
+
'pipe',
|
|
43
|
+
'pipe'
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
let stdout = '';
|
|
47
|
+
let stderr = '';
|
|
48
|
+
let buffer = '';
|
|
49
|
+
// Keyed by part id so streamed updates of the same text part overwrite each other.
|
|
50
|
+
const texts = new Map();
|
|
51
|
+
const handleEvent = (event)=>{
|
|
52
|
+
if (onAction) {
|
|
53
|
+
engine.executePinsList(onAction, {
|
|
54
|
+
action: event,
|
|
55
|
+
...context
|
|
56
|
+
}, `${context.__PATH__}.onAction`);
|
|
57
|
+
}
|
|
58
|
+
if (event?.type === 'text' && event.part?.id) {
|
|
59
|
+
texts.set(event.part.id, event.part.text ?? '');
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const flushLines = ()=>{
|
|
63
|
+
let index = buffer.indexOf('\n');
|
|
64
|
+
while(index !== -1){
|
|
65
|
+
const line = buffer.slice(0, index).trim();
|
|
66
|
+
buffer = buffer.slice(index + 1);
|
|
67
|
+
if (line) {
|
|
68
|
+
try {
|
|
69
|
+
handleEvent(JSON.parse(line));
|
|
70
|
+
} catch {
|
|
71
|
+
// Ignore non-JSON lines (e.g. warnings printed to stdout).
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
index = buffer.indexOf('\n');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const onData = (chunk)=>{
|
|
78
|
+
const s = chunk.toString();
|
|
79
|
+
stdout += s;
|
|
80
|
+
if (debug) {
|
|
81
|
+
process.stdout.write(s);
|
|
82
|
+
}
|
|
83
|
+
if (onStdout) {
|
|
84
|
+
engine.executePinsList(onStdout, {
|
|
85
|
+
chunk: s,
|
|
86
|
+
...context
|
|
87
|
+
}, `${context.__PATH__}.onStdout`);
|
|
88
|
+
}
|
|
89
|
+
if (format === 'json') {
|
|
90
|
+
buffer += s;
|
|
91
|
+
flushLines();
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const onErr = (chunk)=>{
|
|
95
|
+
const s = chunk.toString();
|
|
96
|
+
stderr += s;
|
|
97
|
+
};
|
|
98
|
+
child.stdout?.on('data', onData);
|
|
99
|
+
child.stderr?.on('data', onErr);
|
|
100
|
+
const result = await new Promise((resolve, reject)=>{
|
|
101
|
+
const onExit = (code, signal)=>{
|
|
102
|
+
if (code === 0) {
|
|
103
|
+
if (format === 'json') {
|
|
104
|
+
// Flush any trailing event without a newline, then join the text parts.
|
|
105
|
+
if (buffer.trim()) {
|
|
106
|
+
try {
|
|
107
|
+
handleEvent(JSON.parse(buffer.trim()));
|
|
108
|
+
} catch {
|
|
109
|
+
// Ignore trailing non-JSON output.
|
|
110
|
+
}
|
|
111
|
+
buffer = '';
|
|
112
|
+
}
|
|
113
|
+
resolve(Array.from(texts.values()).join('').trim());
|
|
114
|
+
} else {
|
|
115
|
+
resolve(stdout.trim());
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
const reason = signal ? `signal ${signal}` : `exit code ${code}`;
|
|
119
|
+
reject(new Error(`opencode failed with ${reason}.\n${stderr || stdout}`));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
let timeout;
|
|
123
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
124
|
+
timeout = setTimeout(()=>{
|
|
125
|
+
child.kill('SIGTERM');
|
|
126
|
+
reject(new Error(`opencode timed out after ${timeoutMs} ms`));
|
|
127
|
+
}, timeoutMs);
|
|
128
|
+
}
|
|
129
|
+
child.on('error', (err)=>{
|
|
130
|
+
if (timeout) clearTimeout(timeout);
|
|
131
|
+
reject(err);
|
|
132
|
+
});
|
|
133
|
+
child.on('close', (code, signal)=>{
|
|
134
|
+
if (timeout) clearTimeout(timeout);
|
|
135
|
+
onExit(code, signal);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
const runPrompt = (params, pinsSettingsList, context)=>new OpencodeService().runPrompt(params, pinsSettingsList, context);
|
|
142
|
+
|
|
143
|
+
exports.runPrompt = runPrompt;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|