@gitterm/sdk 0.2.0 → 0.2.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 CHANGED
@@ -517,6 +517,12 @@ remain. The exported types are `AgentPermissionRequest`, `AgentQuestionRequest`,
517
517
  **labels** keyed by question `key`, or one free-text string when `custom` is true:
518
518
  `{ type: "question", answers: { [question.key]: string[] } }`. Every question needs an entry.
519
519
  Or dismiss all of them with `{ type: "question", reject: true }`.
520
+ `custom` defaults to true, matching OpenCode; false only when the agent sets it.
521
+ Questions with `options: []` accept free-text answers when `custom` is true.
522
+
523
+ Question options are normalised at the runtime boundary: labels and descriptions are trimmed,
524
+ OpenCode's copied "Type your own answer" entry is removed and enables `custom: true`, and
525
+ duplicate and blank labels are dropped. Hosted users need the API redeployed for this to take effect.
520
526
 
521
527
  The quick start above shows a complete relay. Rejecting a permission or dismissing a question
522
528
  ends the turn: OpenCode records a failed tool call and the run finishes. A run left
@@ -2,6 +2,36 @@
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { createOpencodeClient as createOpencodeClient2 } from "@opencode-ai/sdk";
4
4
 
5
+ // ../agent-runtime/src/normalize.ts
6
+ var OPENCODE_CUSTOM_OPTION_LABEL = /^type your own answer\W*$/i;
7
+ function normalizeQuestion(question) {
8
+ const options = [];
9
+ const labels = new Set;
10
+ let changed = false;
11
+ let custom = question.custom;
12
+ for (const option of question.options) {
13
+ const label = option.label.trim();
14
+ const description = option.description.trim();
15
+ if (OPENCODE_CUSTOM_OPTION_LABEL.test(label)) {
16
+ custom = true;
17
+ changed = true;
18
+ continue;
19
+ }
20
+ if (!label || labels.has(label)) {
21
+ changed = true;
22
+ continue;
23
+ }
24
+ labels.add(label);
25
+ if (label !== option.label || description !== option.description) {
26
+ changed = true;
27
+ options.push({ ...option, label, description });
28
+ } else {
29
+ options.push(option);
30
+ }
31
+ }
32
+ return changed ? { ...question, options, custom } : question;
33
+ }
34
+
5
35
  // ../agent-runtime/src/opencode.ts
6
36
  import { createOpencodeClient } from "@opencode-ai/sdk";
7
37
 
@@ -426,7 +456,7 @@ function questionRequest(raw) {
426
456
  kind: "question",
427
457
  createdAt: null,
428
458
  toolCallId: asString(tool.callID),
429
- questions: questions.map((question, index) => ({
459
+ questions: questions.map((question, index) => normalizeQuestion({
430
460
  key: `q${index}`,
431
461
  header: asString(question.header) ?? `Question ${index + 1}`,
432
462
  question: asString(question.question) ?? "",
@@ -435,7 +465,7 @@ function questionRequest(raw) {
435
465
  description: asString(option.description) ?? ""
436
466
  })),
437
467
  multiple: question.multiple === true,
438
- custom: question.custom === true
468
+ custom: question.custom !== false
439
469
  }))
440
470
  };
441
471
  }
@@ -714,7 +744,7 @@ function formRequest(raw) {
714
744
  toolCallId: asString(tool.id) ?? asString(tool.callID),
715
745
  questions: fields.map((field, index) => {
716
746
  const key = asString(field.key) ?? `q${index}`;
717
- return {
747
+ return normalizeQuestion({
718
748
  key,
719
749
  header: asString(field.title) ?? key,
720
750
  question: asString(field.description) ?? asString(field.title) ?? key,
@@ -728,8 +758,8 @@ function formRequest(raw) {
728
758
  };
729
759
  }),
730
760
  multiple: field.type === "multiselect",
731
- custom: field.custom === true
732
- };
761
+ custom: field.custom !== false
762
+ });
733
763
  })
734
764
  };
735
765
  }
@@ -842,6 +872,9 @@ function questionAnswers(request, answers) {
842
872
  throw new Error(`Answers must cover exactly the question keys ${JSON.stringify(keys)}`);
843
873
  }
844
874
  return request.questions.map((question) => {
875
+ if (!question.options.length && !question.custom) {
876
+ throw new Error(`Question "${question.key}" has no options and does not accept custom answers`);
877
+ }
845
878
  const selected = answers[question.key];
846
879
  if (!Array.isArray(selected) || !selected.length || !question.multiple && selected.length !== 1) {
847
880
  throw new Error(`Question "${question.key}" requires ${question.multiple ? "at least one answer" : "one answer"}`);
package/dist/types.d.ts CHANGED
@@ -309,6 +309,7 @@ export type AgentQuestionOption = {
309
309
  label: string;
310
310
  description: string;
311
311
  };
312
+ /** Options are normalised: OpenCode's own type-your-own entry, duplicates and blank labels never appear. */
312
313
  export type AgentQuestion = {
313
314
  /** Identifies this question in `AgentRunReply.answers`. Unique within a request. */
314
315
  key: string;
@@ -317,7 +318,10 @@ export type AgentQuestion = {
317
318
  options: AgentQuestionOption[];
318
319
  /** More than one option may be selected. */
319
320
  multiple: boolean;
320
- /** A free-text answer outside `options` is accepted. */
321
+ /**
322
+ * A free-text answer outside `options` is accepted.
323
+ * Defaults to true, matching OpenCode; false only when the agent sets it.
324
+ */
321
325
  custom: boolean;
322
326
  };
323
327
  /** The agent called OpenCode's `question` tool. Answer every question in one reply. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitterm/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "files": [
5
5
  "dist"
6
6
  ],