@alisio/plugin-opencode-go 0.1.0-alpha.2 → 0.1.0-alpha.6

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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { definePlugin } from "@alisio/sdk";
2
2
  import { OpenCodeGoProvider } from "./provider.js";
3
+ import { loadVersion } from "./version.js";
3
4
  const profileString = (request, key) => typeof request.profile[key] === "string" ? String(request.profile[key]) : "";
4
5
  export function createOpenCodeGoPlugin() {
5
6
  return definePlugin({
@@ -7,7 +8,7 @@ export function createOpenCodeGoPlugin() {
7
8
  name: "OpenCode Go",
8
9
  description: "OpenCode Go multi-protocol model gateway",
9
10
  categories: ["model-provider"],
10
- version: "0.1.0-alpha.1",
11
+ version: loadVersion(import.meta.url),
11
12
  apiVersion: 1,
12
13
  setup(api) {
13
14
  api.providers.register({
package/dist/provider.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { loadVersion } from "./version.js";
1
2
  const protocolMap = (groups) => new Map(Object.entries(groups).flatMap(([protocol, models]) => (models ?? []).map((model) => [model, protocol])));
2
3
  const GO_MODELS = protocolMap({
3
4
  responses: [
@@ -48,6 +49,8 @@ export function classifyOpenCodeGoModel(id) {
48
49
  }
49
50
  const dataUrl = (a) => `data:${a.mimeType};base64,${a.data}`;
50
51
  const modelId = (id) => id.replace(/^opencode(?:-go)?\//, "");
52
+ /** Default user agent derives from the package manifest at runtime; never a hardcoded version. */
53
+ const DEFAULT_USER_AGENT = `alisio/${loadVersion(import.meta.url)}`;
51
54
  async function* sse(response) {
52
55
  if (!response.ok)
53
56
  throw new Error(`OpenCode request failed: HTTP ${response.status}`);
@@ -134,7 +137,7 @@ export class OpenCodeGatewayProvider {
134
137
  this.displayName = "OpenCode Go";
135
138
  this.classify = config.classify;
136
139
  this.baseURL = config.baseURL.replace(/\/$/, "");
137
- this.userAgent = config.userAgent ?? "alisio/0.1.0-alpha.1";
140
+ this.userAgent = config.userAgent ?? DEFAULT_USER_AGENT;
138
141
  const url = new URL(this.baseURL);
139
142
  if (url.protocol !== "https:" && url.hostname !== "127.0.0.1" && url.hostname !== "localhost")
140
143
  throw new Error("OpenCode base URL must use HTTPS");
@@ -256,12 +259,23 @@ export class OpenCodeGatewayProvider {
256
259
  if (choice.finish_reason)
257
260
  finish = choice.finish_reason;
258
261
  }
259
- if (finish !== "stop" && finish !== "tool_calls")
262
+ if (finish !== "stop" && finish !== "tool_calls" && finish !== "length")
260
263
  throw new Error(`OpenCode chat response incomplete: ${finish ?? "stream ended"}`);
261
264
  const completed = [...calls.entries()].sort(([a], [b]) => a - b).map(([, call]) => call);
262
265
  if (completed.some((call) => !call.id || !call.name))
263
266
  throw new Error("Incomplete tool call");
264
- yield { type: "completed", message: { role: "assistant", text, calls: completed }, usage };
267
+ if (finish === "length" && !text.trim())
268
+ throw new Error("OpenCode chat response cut off by max output tokens before any usable content; raise limits.maxOutputTokens");
269
+ yield {
270
+ type: "completed",
271
+ message: {
272
+ role: "assistant",
273
+ text,
274
+ calls: completed,
275
+ ...(finish === "length" ? { truncated: true } : {}),
276
+ },
277
+ usage,
278
+ };
265
279
  }
266
280
  responsesInput(messages) {
267
281
  const input = [];
@@ -312,8 +326,39 @@ export class OpenCodeGatewayProvider {
312
326
  yield { type: "text_delta", delta: event.delta };
313
327
  if (event.type === "response.reasoning_summary_text.delta")
314
328
  yield { type: "reasoning_delta", delta: event.delta };
315
- if (["response.failed", "response.incomplete", "error"].includes(event.type))
329
+ if (["response.failed", "error"].includes(event.type))
316
330
  throw new Error(`OpenCode Responses request failed: ${event.type}`);
331
+ if (event.type === "response.incomplete") {
332
+ complete = true;
333
+ const output = event.response.output;
334
+ const calls = output
335
+ .filter((item) => item.type === "function_call")
336
+ .map((item) => ({ id: item.call_id, name: item.name, arguments: item.arguments }));
337
+ const text = output
338
+ .filter((item) => item.type === "message")
339
+ .flatMap((item) => item.content ?? [])
340
+ .filter((item) => item.type === "output_text")
341
+ .map((item) => item.text)
342
+ .join("");
343
+ if (!text.trim())
344
+ throw new Error("OpenCode Responses response cut off by max output tokens before any usable content; raise limits.maxOutputTokens");
345
+ if (calls.some((call) => !call.id || !call.name))
346
+ throw new Error("Incomplete tool call");
347
+ const u = event.response.usage;
348
+ yield {
349
+ type: "completed",
350
+ message: { role: "assistant", text, calls, providerData: output, truncated: true },
351
+ usage: u
352
+ ? {
353
+ input: u.input_tokens ?? 0,
354
+ output: u.output_tokens ?? 0,
355
+ ...(typeof u.input_tokens_details?.cached_tokens === "number"
356
+ ? { cachedInput: u.input_tokens_details.cached_tokens }
357
+ : {}),
358
+ }
359
+ : undefined,
360
+ };
361
+ }
317
362
  if (event.type === "response.completed") {
318
363
  complete = true;
319
364
  const output = event.response.output;
@@ -456,7 +501,8 @@ export class OpenCodeGatewayProvider {
456
501
  stopReason = event.delta?.stop_reason ?? stopReason;
457
502
  }
458
503
  }
459
- if (stopReason !== "end_turn" && stopReason !== "tool_use")
504
+ const truncated = stopReason === "max_tokens";
505
+ if (!truncated && stopReason !== "end_turn" && stopReason !== "tool_use")
460
506
  throw new Error(`OpenCode Messages response incomplete: ${stopReason ?? "stream ended"}`);
461
507
  const calls = [];
462
508
  for (let index = 0; index < blocks.length; index++) {
@@ -465,7 +511,24 @@ export class OpenCodeGatewayProvider {
465
511
  continue;
466
512
  const args = argumentParts.get(index) || JSON.stringify(block.input ?? {});
467
513
  calls.push({ id: block.id, name: block.name, arguments: args });
468
- block.input = JSON.parse(args || "{}");
514
+ try {
515
+ block.input = JSON.parse(args || "{}");
516
+ }
517
+ catch {
518
+ if (!truncated)
519
+ throw new Error("Incomplete tool call");
520
+ throw new Error(`OpenCode Messages response incomplete: tool call arguments cut off for ${block.name}`);
521
+ }
522
+ }
523
+ if (truncated) {
524
+ if (!text.trim())
525
+ throw new Error("OpenCode Messages response cut off by max output tokens before any usable content; raise limits.maxOutputTokens");
526
+ yield {
527
+ type: "completed",
528
+ message: { role: "assistant", text, calls, providerData: blocks, truncated: true },
529
+ usage: { input, output },
530
+ };
531
+ return;
469
532
  }
470
533
  yield {
471
534
  type: "completed",
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Reads this plugin package's version at runtime so plugin metadata, user-agent defaults and CLI
3
+ * reports stay in sync with the published package without a build-time constant to update.
4
+ * The manifest is resolved relative to the module (`src/version.ts` → `../package.json` in the
5
+ * source tree; `dist/version.js` → `../package.json` of the installed package). Standalone
6
+ * binaries inject the version at build time via ALISIO_PACKAGE_VERSION (scripts/binary-build.ts).
7
+ */
8
+ export declare function loadVersion(fromHere: string): string;
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Fallback used when neither the build-time injection nor the package manifest is readable
6
+ * (e.g. an unpackaged embed). Keep it a development marker, never a publish literal, so it
7
+ * cannot silently desync from the published package.
8
+ */
9
+ const FALLBACK = "dev";
10
+ /**
11
+ * Reads this plugin package's version at runtime so plugin metadata, user-agent defaults and CLI
12
+ * reports stay in sync with the published package without a build-time constant to update.
13
+ * The manifest is resolved relative to the module (`src/version.ts` → `../package.json` in the
14
+ * source tree; `dist/version.js` → `../package.json` of the installed package). Standalone
15
+ * binaries inject the version at build time via ALISIO_PACKAGE_VERSION (scripts/binary-build.ts).
16
+ */
17
+ export function loadVersion(fromHere) {
18
+ if (process.env.ALISIO_PACKAGE_VERSION)
19
+ return process.env.ALISIO_PACKAGE_VERSION;
20
+ try {
21
+ const manifest = join(dirname(fileURLToPath(fromHere)), "..", "package.json");
22
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
23
+ return typeof parsed.version === "string" && parsed.version ? parsed.version : FALLBACK;
24
+ }
25
+ catch {
26
+ return FALLBACK;
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/plugin-opencode-go",
3
- "version": "0.1.0-alpha.2",
3
+ "version": "0.1.0-alpha.6",
4
4
  "description": "Built-in OpenCode Go model provider for Alisio.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,10 +21,10 @@
21
21
  "node": ">=22.16"
22
22
  },
23
23
  "peerDependencies": {
24
- "@alisio/sdk": "^0.1.0-alpha.2"
24
+ "@alisio/sdk": "^0.1.0-alpha.5"
25
25
  },
26
26
  "devDependencies": {
27
- "@alisio/sdk": "0.1.0-alpha.2"
27
+ "@alisio/sdk": "0.1.0-alpha.5"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public",