@owlmeans/llm-common 0.1.18-rc.2 → 0.1.18-rc.21

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.
Files changed (52) hide show
  1. package/README.md +2 -2
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/llm-common/SKILL.md +29 -6
  4. package/build/consts.d.ts +22 -1
  5. package/build/consts.d.ts.map +1 -1
  6. package/build/consts.js +21 -0
  7. package/build/consts.js.map +1 -1
  8. package/build/delegate/index.d.ts +2 -0
  9. package/build/delegate/index.d.ts.map +1 -0
  10. package/build/delegate/index.js +2 -0
  11. package/build/delegate/index.js.map +1 -0
  12. package/build/delegate/types.d.ts +108 -0
  13. package/build/delegate/types.d.ts.map +1 -0
  14. package/build/delegate/types.js +39 -0
  15. package/build/delegate/types.js.map +1 -0
  16. package/build/files/types.d.ts +10 -0
  17. package/build/files/types.d.ts.map +1 -1
  18. package/build/index.d.ts +2 -0
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +2 -0
  21. package/build/index.js.map +1 -1
  22. package/build/inquiry/consts.d.ts +48 -0
  23. package/build/inquiry/consts.d.ts.map +1 -0
  24. package/build/inquiry/consts.js +50 -0
  25. package/build/inquiry/consts.js.map +1 -0
  26. package/build/inquiry/index.d.ts +4 -0
  27. package/build/inquiry/index.d.ts.map +1 -0
  28. package/build/inquiry/index.js +3 -0
  29. package/build/inquiry/index.js.map +1 -0
  30. package/build/inquiry/types.d.ts +72 -0
  31. package/build/inquiry/types.d.ts.map +1 -0
  32. package/build/inquiry/types.js +2 -0
  33. package/build/inquiry/types.js.map +1 -0
  34. package/build/inquiry/utils.d.ts +45 -0
  35. package/build/inquiry/utils.d.ts.map +1 -0
  36. package/build/inquiry/utils.js +72 -0
  37. package/build/inquiry/utils.js.map +1 -0
  38. package/build/types.d.ts +35 -2
  39. package/build/types.d.ts.map +1 -1
  40. package/package.json +2 -2
  41. package/src/consts.ts +22 -0
  42. package/src/delegate/index.ts +1 -0
  43. package/src/delegate/types.ts +116 -0
  44. package/src/files/types.ts +11 -0
  45. package/src/index.ts +2 -0
  46. package/src/inquiry/consts.ts +52 -0
  47. package/src/inquiry/index.ts +3 -0
  48. package/src/inquiry/types.ts +77 -0
  49. package/src/inquiry/utils.ts +84 -0
  50. package/src/types.ts +35 -2
  51. package/tests/inquiry.spec.ts +112 -0
  52. package/tsconfig.json +3 -1
@@ -0,0 +1,45 @@
1
+ import type { Inquiry, InquiryAnswer } from './types.js';
2
+ /**
3
+ * Pure helpers over an inquiry and its answer — no IO, no state, no clock.
4
+ *
5
+ * They are here rather than in the runtime because every layer needs the same reading of an
6
+ * answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
7
+ * screen that finally shows it. A second reading of "was this answered" is a second contract.
8
+ */
9
+ /**
10
+ * What to assume when nobody answers: the question's own default, or a decline.
11
+ *
12
+ * A decline is an ANSWER — the run carries on and records what it assumed — which is why this
13
+ * never throws and never returns null.
14
+ */
15
+ export declare const defaultAnswerFor: (inquiry: Inquiry) => InquiryAnswer;
16
+ /**
17
+ * The one thing an answer says, as a string — the first chosen value, else the free text, else
18
+ * `null` when it says nothing at all (a decline, or an empty answer).
19
+ */
20
+ export declare const answeredWith: (answer: InquiryAnswer) => string | null;
21
+ /** Nobody could decide. Distinct from an empty answer — see {@link answeredWith}. */
22
+ export declare const isDeclined: (answer: InquiryAnswer) => boolean;
23
+ /**
24
+ * Cut an answer's free TEXT to the ceiling and SAY SO.
25
+ *
26
+ * Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
27
+ * question's own option values — so slicing one does not degrade an answer, it silently replaces
28
+ * it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
29
+ * what the person chose. An over-long value is a defect upstream rather than a long answer (the
30
+ * connector's schema refuses one outright instead of shortening it), so it travels on whole and is
31
+ * REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
32
+ * what the person gave".
33
+ */
34
+ export declare const capAnswer: (answer: InquiryAnswer, max?: number) => InquiryAnswer;
35
+ /**
36
+ * The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
37
+ * {@link INQUIRY_STATE_TEXT_CHARS}.
38
+ *
39
+ * The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
40
+ * asks several questions still holds a state made of keys rather than of paragraphs.
41
+ */
42
+ export declare const stateAnswerOf: (answer: InquiryAnswer) => InquiryAnswer;
43
+ /** One-line label of a question — for a note, a trace line or a run row. */
44
+ export declare const renderInquiry: (inquiry: Inquiry) => string;
45
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/inquiry/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAExD;;;;;;GAMG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,YAAa,OAAO,KAAG,aAGL,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,YAAY,WAAY,aAAa,KAAG,MAAM,GAAG,IAM7D,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,UAAU,WAAY,aAAa,KAAG,OAAmC,CAAA;AAEtF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,WACZ,aAAa,QAAO,MAAM,KACjC,aAYF,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,WAAY,aAAa,KAAG,aAG1C,CAAA;AAEZ,4EAA4E;AAC5E,eAAO,MAAM,aAAa,YAAa,OAAO,KAAG,MAMhD,CAAA"}
@@ -0,0 +1,72 @@
1
+ import { DEFAULT_INQUIRY_ANSWER_CHARS, INQUIRY_STATE_TEXT_CHARS } from './consts.js';
2
+ /**
3
+ * Pure helpers over an inquiry and its answer — no IO, no state, no clock.
4
+ *
5
+ * They are here rather than in the runtime because every layer needs the same reading of an
6
+ * answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
7
+ * screen that finally shows it. A second reading of "was this answered" is a second contract.
8
+ */
9
+ /**
10
+ * What to assume when nobody answers: the question's own default, or a decline.
11
+ *
12
+ * A decline is an ANSWER — the run carries on and records what it assumed — which is why this
13
+ * never throws and never returns null.
14
+ */
15
+ export const defaultAnswerFor = (inquiry) => inquiry.default != null
16
+ ? { inquiryId: inquiry.id, value: inquiry.default }
17
+ : { inquiryId: inquiry.id, declined: true };
18
+ /**
19
+ * The one thing an answer says, as a string — the first chosen value, else the free text, else
20
+ * `null` when it says nothing at all (a decline, or an empty answer).
21
+ */
22
+ export const answeredWith = (answer) => {
23
+ const value = Array.isArray(answer.value) ? answer.value[0] : answer.value;
24
+ if (value != null && value !== '')
25
+ return value;
26
+ if (answer.text != null && answer.text !== '')
27
+ return answer.text;
28
+ return null;
29
+ };
30
+ /** Nobody could decide. Distinct from an empty answer — see {@link answeredWith}. */
31
+ export const isDeclined = (answer) => answer.declined === true;
32
+ /**
33
+ * Cut an answer's free TEXT to the ceiling and SAY SO.
34
+ *
35
+ * Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
36
+ * question's own option values — so slicing one does not degrade an answer, it silently replaces
37
+ * it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
38
+ * what the person chose. An over-long value is a defect upstream rather than a long answer (the
39
+ * connector's schema refuses one outright instead of shortening it), so it travels on whole and is
40
+ * REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
41
+ * what the person gave".
42
+ */
43
+ export const capAnswer = (answer, max = DEFAULT_INQUIRY_ANSWER_CHARS) => {
44
+ const text = answer.text != null && answer.text.length > max
45
+ ? answer.text.slice(0, max)
46
+ : undefined;
47
+ const values = Array.isArray(answer.value)
48
+ ? answer.value
49
+ : answer.value != null ? [answer.value] : [];
50
+ const oversized = values.some(value => value.length > max);
51
+ if (text == null && !oversized)
52
+ return answer;
53
+ return { ...answer, ...(text != null ? { text } : {}), truncated: true };
54
+ };
55
+ /**
56
+ * The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
57
+ * {@link INQUIRY_STATE_TEXT_CHARS}.
58
+ *
59
+ * The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
60
+ * asks several questions still holds a state made of keys rather than of paragraphs.
61
+ */
62
+ export const stateAnswerOf = (answer) => answer.text != null && answer.text.length > INQUIRY_STATE_TEXT_CHARS
63
+ ? { ...answer, text: answer.text.slice(0, INQUIRY_STATE_TEXT_CHARS), truncated: true }
64
+ : answer;
65
+ /** One-line label of a question — for a note, a trace line or a run row. */
66
+ export const renderInquiry = (inquiry) => {
67
+ const options = inquiry.options != null && inquiry.options.length > 0
68
+ ? ` (${inquiry.options.map(option => option.value).join(' | ')})`
69
+ : '';
70
+ return `[${inquiry.kind}] ${inquiry.question}${options}`.replace(/\s+/g, ' ').trim();
71
+ };
72
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/inquiry/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAGpF;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAgB,EAAiB,EAAE,CAClE,OAAO,CAAC,OAAO,IAAI,IAAI;IACrB,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE;IACnD,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAqB,EAAiB,EAAE;IACnE,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAA;IAC1E,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,KAAK,CAAA;IAC/C,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC,IAAI,CAAA;IAEjE,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAqB,EAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAA;AAEtF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,MAAqB,EAAE,GAAG,GAAW,4BAA4B,EAClD,EAAE;IACjB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG;QAC1D,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QAC3B,CAAC,CAAC,SAAS,CAAA;IACb,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QACxC,CAAC,CAAC,MAAM,CAAC,KAAK;QACd,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;IAE1D,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS;QAAE,OAAO,MAAM,CAAA;IAE7C,OAAO,EAAE,GAAG,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAA;AAC1E,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAqB,EAAiB,EAAE,CACpE,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,wBAAwB;IAClE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE;IACtF,CAAC,CAAC,MAAM,CAAA;AAEZ,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAU,EAAE;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;QACnE,CAAC,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG;QACjE,CAAC,CAAC,EAAE,CAAA;IAEN,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,QAAQ,GAAG,OAAO,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;AACtF,CAAC,CAAA"}
package/build/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ExecutionEffort, ExecutionLevel, PromptBlock } from './consts.js';
2
+ import type { InquiryConfig } from './inquiry/types.js';
2
3
  /**
3
4
  * Free-form observability metadata attached to every model call — forwarded to the
4
5
  * inference provider as run metadata and recorded on every spectator entry. Kept
@@ -29,6 +30,12 @@ export interface ModelConfigPatch {
29
30
  maxTokensCap?: number;
30
31
  topP?: number;
31
32
  disableThinking?: boolean;
33
+ /** Total window the model accepts (input + output). Informational / validation only. */
34
+ contextWindow?: number;
35
+ /** What the PROVIDER can emit in one request. Hard ceiling for `maxTokens`/`maxTokensCap`. */
36
+ maxOutput?: number;
37
+ /** The window is shared between input and output rather than input-only. */
38
+ combinedWindow?: boolean;
32
39
  }
33
40
  /** A JSON-safe model override: a config alias, or a partial config patch. */
34
41
  export type ModelConfigOverride = string | ModelConfigPatch;
@@ -44,6 +51,12 @@ export interface ModelPolicy {
44
51
  roleOverrides?: Partial<Record<ModelRole, ModelRole>>;
45
52
  /** "Pin a role to a specific model/config" — alias or partial config override. */
46
53
  modelOverrides?: Partial<Record<ModelRole, ModelConfigOverride>>;
54
+ /**
55
+ * Role resolved by `ExecutionService.utility` for cheap side calls. Defaults to
56
+ * `UTILITY_ROLE`; name another alias when the deployment calls its cheap tier
57
+ * something else. `roleOverrides` still applies on top of whichever one is used.
58
+ */
59
+ utilityRole?: ModelRole;
47
60
  }
48
61
  /**
49
62
  * Lifetime of a provider-side prompt cache entry. `'1h'` costs roughly twice as much to
@@ -119,10 +132,23 @@ export interface ExecutionState {
119
132
  policy: ModelPolicy;
120
133
  /** Role + skills for this level; merged downward by `ExecutionService`. */
121
134
  prompt?: PromptPolicy;
135
+ /**
136
+ * How this run may put a question to a person. Serializable, and deliberately NOT a
137
+ * collaborator: a run that is resumed days later must ask through the same channel, under the
138
+ * same policy, as the one that parked it.
139
+ */
140
+ inquiry?: InquiryConfig;
122
141
  }
123
- /** Resumable state of a task-level execution. */
142
+ /**
143
+ * The task level's own fields.
144
+ *
145
+ * `phase`, `completed` and `cursor` are LABELS — for a trace line, a prompt, a log — and never a
146
+ * workflow position. Recoverable position lives on a pipeline run row (`@owlmeans/agent`), which is
147
+ * a single authority; an execution that also claimed to know where a run stood would be a second
148
+ * one, and the two would disagree the first time a step wrote only one of them.
149
+ */
124
150
  export interface TaskExecutionState extends ExecutionState {
125
- /** Abstract workflow position for checkpoint/resume. */
151
+ /** A label for the stage a task considers itself in. Never read back to decide anything. */
126
152
  phase?: string;
127
153
  completed?: string[];
128
154
  cursor?: string;
@@ -171,11 +197,18 @@ export interface NullCapture {
171
197
  tool_calls?: unknown;
172
198
  } | null;
173
199
  diagnostics: {
200
+ /** Whatever the provider called it — OpenAI's `finish_reason` or Anthropic's `stop_reason`. */
174
201
  finishReason?: string;
175
202
  inputTokens?: number;
176
203
  outputTokens?: number;
177
204
  reasoningTokens?: number;
178
205
  contentEmpty: boolean;
206
+ /**
207
+ * Content arrived, but none of it was text — the shape of an answer that was all reasoning.
208
+ * Distinguishes "spent the budget thinking" from "returned nothing at all", which
209
+ * `contentEmpty` alone cannot.
210
+ */
211
+ thinkingOnly?: boolean;
179
212
  hadToolCall: boolean;
180
213
  };
181
214
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/llm-common",
3
- "version": "0.1.18-rc.2",
3
+ "version": "0.1.18-rc.21",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "devDependencies": {
24
- "@langchain/core": "^1.1.39",
24
+ "@langchain/core": "^1.2.9",
25
25
  "@owlmeans/dep-config": "workspace:*",
26
26
  "nodemon": "^3.1.14",
27
27
  "typescript": "^7.0.2"
package/src/consts.ts CHANGED
@@ -11,6 +11,20 @@ export enum ModelProvider {
11
11
  Anthropic = 'anthropic',
12
12
  /** Any OpenAI-compatible endpoint — OpenRouter, HuggingFace router, Together, vLLM, … */
13
13
  Compatible = 'compatible',
14
+ /**
15
+ * No endpoint at all: the call is handed to whoever holds the execution.
16
+ *
17
+ * A delegated model does not talk to a provider. It packages the call — the system prompt, the
18
+ * conversation, the tools or the schema — and hands it to a transport the application seated,
19
+ * which carries it to something outside this process entirely: a coding agent driving the
20
+ * application through a connector, a human, a test. The answer comes back the same way and is
21
+ * turned into a completion the rest of the stack cannot tell apart from a provider's.
22
+ *
23
+ * It exists so that "who performs this call" can be a property of the SESSION rather than of the
24
+ * code: the same pipeline, the same prompts and the same retry rules, billed to somebody else's
25
+ * model.
26
+ */
27
+ Delegated = 'delegated',
14
28
  }
15
29
 
16
30
  /**
@@ -96,3 +110,11 @@ export const PROMPT_BLOCK_ORDER: readonly PromptBlock[] = [
96
110
 
97
111
  /** Sort weight of a skill that declares none — see `SkillDefinition.order`. */
98
112
  export const DEFAULT_SKILL_ORDER = 100
113
+
114
+ /**
115
+ * Conventional {@link ModelRole} for the cheap side calls the layer makes on its own
116
+ * behalf — a relevance pick, a classification, a one-line judgement — rather than for the
117
+ * work a caller asked for. A deployment that names its cheap tier differently points
118
+ * `ModelPolicy.utilityRole` at its own alias; nothing else has to change.
119
+ */
120
+ export const UTILITY_ROLE = 'utility'
@@ -0,0 +1 @@
1
+ export * from './types.js'
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The serializable form of one model call, for a performer outside this process.
3
+ *
4
+ * A delegated call cannot pass a `BaseChatModel` or a langchain message anywhere: the thing that
5
+ * answers it is a coding agent on somebody's laptop, a test double, or a person. So the call is
6
+ * reduced to what any of them can act on — a persona, a conversation, and the shape the answer
7
+ * must take — and everything provider-specific is left behind.
8
+ *
9
+ * There is no vocabulary here from whatever pipeline asked. A `role` and a `tier` travel because
10
+ * the performer has to choose a model; nothing else about the caller does.
11
+ */
12
+
13
+ /** What the answer must be. */
14
+ export enum DelegatedMode {
15
+ /** Prose or code. */
16
+ Text = 'text',
17
+ /** Exactly one JSON object satisfying `outputSchema`. */
18
+ Json = 'json',
19
+ /** A list of calls chosen from `tools`. */
20
+ Tools = 'tools',
21
+ }
22
+
23
+ /** Who a message came from. Deliberately the four roles every chat API agrees on. */
24
+ export enum DelegatedRole {
25
+ System = 'system',
26
+ User = 'user',
27
+ Assistant = 'assistant',
28
+ Tool = 'tool',
29
+ }
30
+
31
+ /** What shape an answer came back in. */
32
+ export enum DelegatedResultKind {
33
+ Text = 'text',
34
+ Json = 'json',
35
+ ToolCalls = 'tool-calls',
36
+ /** The performer could not answer. Treated as a malformed answer: asked again, with the reason. */
37
+ Error = 'error',
38
+ }
39
+
40
+ export interface DelegatedToolCall {
41
+ id?: string
42
+ name: string
43
+ args: Record<string, unknown>
44
+ }
45
+
46
+ export interface DelegatedMessage {
47
+ role: DelegatedRole
48
+ content: string
49
+ /** Assistant turns that called tools. */
50
+ toolCalls?: DelegatedToolCall[]
51
+ /** Tool turns answer one call, and name the tool they answer. */
52
+ toolCallId?: string
53
+ name?: string
54
+ }
55
+
56
+ export interface DelegatedTool {
57
+ name: string
58
+ description?: string
59
+ /** JSON Schema of the arguments. */
60
+ parameters: Record<string, unknown>
61
+ }
62
+
63
+ export type DelegatedToolChoice = 'auto' | 'none' | { name: string }
64
+
65
+ export interface DelegatedTask {
66
+ id: string
67
+ /** Which transport is expected to answer it — the key the application seated it under. */
68
+ delegate: string
69
+ /** The performer's role name, for its own logging. Never load-bearing. */
70
+ role?: string
71
+ /** Which power class the performer should run this on. */
72
+ tier?: string
73
+ /** 0-based. Above zero means a previous answer was refused; `feedback` says why. */
74
+ attempt: number
75
+ mode: DelegatedMode
76
+ system?: string
77
+ messages: DelegatedMessage[]
78
+ tools?: DelegatedTool[]
79
+ toolChoice?: DelegatedToolChoice
80
+ outputSchema?: Record<string, unknown>
81
+ /** A soft cap, stated so a performer can size its own call. */
82
+ maxOutputChars?: number
83
+ feedback?: string
84
+ /** ISO. A performer past this may say so rather than answer. */
85
+ expiresAt?: string
86
+ }
87
+
88
+ export interface DelegatedUsage {
89
+ inputTokens?: number
90
+ outputTokens?: number
91
+ }
92
+
93
+ export interface DelegatedResult {
94
+ taskId: string
95
+ kind: DelegatedResultKind
96
+ text?: string
97
+ json?: unknown
98
+ toolCalls?: DelegatedToolCall[]
99
+ error?: string
100
+ /** What the performer spent. Recorded; it costs this deployment nothing. */
101
+ usage?: DelegatedUsage
102
+ /** What the performer actually ran. Display only. */
103
+ model?: string
104
+ }
105
+
106
+ /**
107
+ * How a delegated call reaches its performer.
108
+ *
109
+ * One method, because that is the whole seam: everything about routing, waiting, redelivery and
110
+ * giving up belongs to whoever implements it. A transport that cannot serve the call must THROW
111
+ * rather than answer with an error result — an error result is a bad answer, which is retried,
112
+ * while a transport that is gone is terminal.
113
+ */
114
+ export interface DelegateTransport {
115
+ dispatch: (task: DelegatedTask, signal?: AbortSignal) => Promise<DelegatedResult>
116
+ }
@@ -17,6 +17,17 @@
17
17
  * that stops a rich helper from satisfying this one.
18
18
  */
19
19
  export interface LlmFileProvider {
20
+ /**
21
+ * Stable identity of WHAT this provider reads — a project root, a sandbox id.
22
+ *
23
+ * A prompt plugin that caches resolved reads across calls has nothing else to key on:
24
+ * providers are late-bound and often rebuilt per request, so object identity says
25
+ * nothing, and a cache shared between two projects serves the first one's files to the
26
+ * second. A provider that supplies no key is treated as uncacheable rather than as one
27
+ * more anonymous member of a shared bucket.
28
+ */
29
+ key?: string
30
+
20
31
  /** Read a file relative to the root. With `noThrow`, a missing file yields `''`. */
21
32
  readFile: (filePath: string, noThrow?: boolean) => Promise<string>
22
33
 
package/src/index.ts CHANGED
@@ -4,3 +4,5 @@ export type * from './types.js'
4
4
  export type * from './spectator/types.js'
5
5
  export type * from './files/types.js'
6
6
  export * from './files/utils.js'
7
+ export * from './delegate/index.js'
8
+ export * from './inquiry/index.js'
@@ -0,0 +1,52 @@
1
+ /**
2
+ * What shape of answer a question expects. Deliberately three, because a question a person is
3
+ * asked mid-run is answered in seconds or not at all: pick one of these, say it in your own
4
+ * words, or say yes/no. Anything richer is a form, and a form belongs to an application screen.
5
+ */
6
+ export enum InquiryKind {
7
+ Choice = 'choice',
8
+ Text = 'text',
9
+ Confirm = 'confirm',
10
+ }
11
+
12
+ /**
13
+ * What a run is allowed to do when it needs a decision that is not its own.
14
+ *
15
+ * - `Ask` — put it to a person through the seated transport and wait.
16
+ * - `Default` — assume the question's own default (or record a decline) and carry on. The caller
17
+ * is expected to RECORD the assumption; a run nobody is watching must never block.
18
+ * - `Refuse` — nobody may be asked at all; the attempt is an error.
19
+ */
20
+ export enum InquiryPolicy {
21
+ Ask = 'ask',
22
+ Default = 'default',
23
+ Refuse = 'refuse',
24
+ }
25
+
26
+ /**
27
+ * The ONE ceiling on a stored answer, in characters. An answer is a decision, not a document.
28
+ *
29
+ * Every layer that carries an answer references this constant rather than choosing its own:
30
+ * `viable-common`'s `CONNECT_INQUIRY_MAX_TEXT` (the wire copy) equals it, `capAnswer` enforces it,
31
+ * and the connector's answer schema caps `text` at it. Three ceilings for one value is how a
32
+ * user's answer gets accepted on the wire and silently halved further in.
33
+ */
34
+ export const DEFAULT_INQUIRY_ANSWER_CHARS = 2_000
35
+
36
+ /** How many options one question may offer. Beyond this it is not a question. */
37
+ export const DEFAULT_INQUIRY_OPTIONS = 12
38
+
39
+ /**
40
+ * How much of an answer's free text a resumable pipeline STATE may hold.
41
+ *
42
+ * A pipeline state is keys, markers and paths — a couple of full-size answers would make it prose,
43
+ * which is the invariant the whole pipeline design rests on. The decision (`value`/`declined`)
44
+ * stays whole; the prose is cut here and belongs in whatever document the application keeps for
45
+ * it (`stateAnswerOf`).
46
+ */
47
+ export const INQUIRY_STATE_TEXT_CHARS = 200
48
+
49
+ /** The answer value of a confirmed {@link InquiryKind.Confirm}. */
50
+ export const CONFIRM_YES = 'yes'
51
+ /** The answer value of a refused {@link InquiryKind.Confirm}. */
52
+ export const CONFIRM_NO = 'no'
@@ -0,0 +1,3 @@
1
+ export * from './consts.js'
2
+ export type * from './types.js'
3
+ export * from './utils.js'
@@ -0,0 +1,77 @@
1
+ import type { InquiryKind, InquiryPolicy } from './consts.js'
2
+
3
+ /**
4
+ * One question put to a person while a run is in flight, and the answer that comes back.
5
+ *
6
+ * Everything here is serializable for the same reason a delegated task is: whoever answers is
7
+ * outside this process — a browser dialog, a coding agent driving the application through a
8
+ * connector, a test double — and the question may outlive the process that asked it, parked on a
9
+ * pipeline run row until somebody comes back to it.
10
+ */
11
+
12
+ export interface InquiryOption {
13
+ value: string
14
+ label: string
15
+ description?: string
16
+ }
17
+
18
+ export interface Inquiry {
19
+ /** Stable id, chosen by whoever asks. The ONLY thing that routes an answer back. */
20
+ id: string
21
+ kind: InquiryKind
22
+ /** One question, in plain words. */
23
+ question: string
24
+ /** One or two sentences of background. Never the whole task. */
25
+ context?: string
26
+ /** Required for {@link InquiryKind.Choice}; ignored otherwise. */
27
+ options?: InquiryOption[]
28
+ multiple?: boolean
29
+ /** A `Choice` the answerer may answer in their own words instead. */
30
+ allowText?: boolean
31
+ /** What to assume when nobody answers. {@link InquiryPolicy.Default} returns exactly this. */
32
+ default?: string | string[]
33
+ expiresAt?: string
34
+ }
35
+
36
+ export interface InquiryAnswer {
37
+ inquiryId: string
38
+ value?: string | string[]
39
+ text?: string
40
+ /** Nobody could decide. A legitimate ANSWER, never a failure. */
41
+ declined?: boolean
42
+ /**
43
+ * This answer is not exactly the one that was given. Never an answerer's own flag.
44
+ *
45
+ * Two writers, two ceilings: `capAnswer` cuts the text to `DEFAULT_INQUIRY_ANSWER_CHARS` (and
46
+ * raises this WITHOUT cutting when a `value` arrived over that ceiling, since a shortened
47
+ * identifier matches no option), while `stateAnswerOf` cuts the text again to the much smaller
48
+ * `INQUIRY_STATE_TEXT_CHARS` a pipeline state may hold. So a flag read back off a resumed run
49
+ * says the state's copy is short — not that the person hit the answer ceiling.
50
+ *
51
+ * It exists so no cut is silent: whoever records the answer can say that the rest of it was
52
+ * dropped, instead of the answerer discovering it in the work that followed.
53
+ */
54
+ truncated?: boolean
55
+ }
56
+
57
+ /**
58
+ * How a question reaches a person.
59
+ *
60
+ * One method, like `DelegateTransport` beside it, and for the same reason: routing, waiting,
61
+ * redelivery and giving up all belong to whoever implements it. A transport that cannot serve the
62
+ * question must THROW rather than answer — a declined answer is a decision, while a channel that
63
+ * is not there is terminal, and the two must never look alike.
64
+ */
65
+ export interface InquiryTransport {
66
+ ask: (inquiry: Inquiry, signal?: AbortSignal) => Promise<InquiryAnswer>
67
+ }
68
+
69
+ /**
70
+ * How one run may put a question to a person. Carried on an execution's serializable state, so a
71
+ * resumed run keeps the channel and the policy it was started with.
72
+ */
73
+ export interface InquiryConfig {
74
+ /** The key the application seated an {@link InquiryTransport} under. */
75
+ transport?: string
76
+ policy: InquiryPolicy
77
+ }
@@ -0,0 +1,84 @@
1
+ import { DEFAULT_INQUIRY_ANSWER_CHARS, INQUIRY_STATE_TEXT_CHARS } from './consts.js'
2
+ import type { Inquiry, InquiryAnswer } from './types.js'
3
+
4
+ /**
5
+ * Pure helpers over an inquiry and its answer — no IO, no state, no clock.
6
+ *
7
+ * They are here rather than in the runtime because every layer needs the same reading of an
8
+ * answer: the execution service, the pipeline runner, the `ask_user` tool, the connector and the
9
+ * screen that finally shows it. A second reading of "was this answered" is a second contract.
10
+ */
11
+
12
+ /**
13
+ * What to assume when nobody answers: the question's own default, or a decline.
14
+ *
15
+ * A decline is an ANSWER — the run carries on and records what it assumed — which is why this
16
+ * never throws and never returns null.
17
+ */
18
+ export const defaultAnswerFor = (inquiry: Inquiry): InquiryAnswer =>
19
+ inquiry.default != null
20
+ ? { inquiryId: inquiry.id, value: inquiry.default }
21
+ : { inquiryId: inquiry.id, declined: true }
22
+
23
+ /**
24
+ * The one thing an answer says, as a string — the first chosen value, else the free text, else
25
+ * `null` when it says nothing at all (a decline, or an empty answer).
26
+ */
27
+ export const answeredWith = (answer: InquiryAnswer): string | null => {
28
+ const value = Array.isArray(answer.value) ? answer.value[0] : answer.value
29
+ if (value != null && value !== '') return value
30
+ if (answer.text != null && answer.text !== '') return answer.text
31
+
32
+ return null
33
+ }
34
+
35
+ /** Nobody could decide. Distinct from an empty answer — see {@link answeredWith}. */
36
+ export const isDeclined = (answer: InquiryAnswer): boolean => answer.declined === true
37
+
38
+ /**
39
+ * Cut an answer's free TEXT to the ceiling and SAY SO.
40
+ *
41
+ * Only the prose is cut. A `value` IS the decision — for a choice it has to equal one of the
42
+ * question's own option values — so slicing one does not degrade an answer, it silently replaces
43
+ * it with an identifier nobody offered and nothing matches, which `answeredWith` then hands on as
44
+ * what the person chose. An over-long value is a defect upstream rather than a long answer (the
45
+ * connector's schema refuses one outright instead of shortening it), so it travels on whole and is
46
+ * REPORTED through `truncated` — the flag every consumer already reads as "this is not exactly
47
+ * what the person gave".
48
+ */
49
+ export const capAnswer = (
50
+ answer: InquiryAnswer, max: number = DEFAULT_INQUIRY_ANSWER_CHARS
51
+ ): InquiryAnswer => {
52
+ const text = answer.text != null && answer.text.length > max
53
+ ? answer.text.slice(0, max)
54
+ : undefined
55
+ const values = Array.isArray(answer.value)
56
+ ? answer.value
57
+ : answer.value != null ? [answer.value] : []
58
+ const oversized = values.some(value => value.length > max)
59
+
60
+ if (text == null && !oversized) return answer
61
+
62
+ return { ...answer, ...(text != null ? { text } : {}), truncated: true }
63
+ }
64
+
65
+ /**
66
+ * The copy of an answer a resumable pipeline STATE keeps: the decision whole, the prose cut to
67
+ * {@link INQUIRY_STATE_TEXT_CHARS}.
68
+ *
69
+ * The full answer goes back to whoever asked; only this reduced one is persisted, so a run that
70
+ * asks several questions still holds a state made of keys rather than of paragraphs.
71
+ */
72
+ export const stateAnswerOf = (answer: InquiryAnswer): InquiryAnswer =>
73
+ answer.text != null && answer.text.length > INQUIRY_STATE_TEXT_CHARS
74
+ ? { ...answer, text: answer.text.slice(0, INQUIRY_STATE_TEXT_CHARS), truncated: true }
75
+ : answer
76
+
77
+ /** One-line label of a question — for a note, a trace line or a run row. */
78
+ export const renderInquiry = (inquiry: Inquiry): string => {
79
+ const options = inquiry.options != null && inquiry.options.length > 0
80
+ ? ` (${inquiry.options.map(option => option.value).join(' | ')})`
81
+ : ''
82
+
83
+ return `[${inquiry.kind}] ${inquiry.question}${options}`.replace(/\s+/g, ' ').trim()
84
+ }