@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.
- package/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/llm-common/SKILL.md +29 -6
- package/build/consts.d.ts +22 -1
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +21 -0
- package/build/consts.js.map +1 -1
- package/build/delegate/index.d.ts +2 -0
- package/build/delegate/index.d.ts.map +1 -0
- package/build/delegate/index.js +2 -0
- package/build/delegate/index.js.map +1 -0
- package/build/delegate/types.d.ts +108 -0
- package/build/delegate/types.d.ts.map +1 -0
- package/build/delegate/types.js +39 -0
- package/build/delegate/types.js.map +1 -0
- package/build/files/types.d.ts +10 -0
- package/build/files/types.d.ts.map +1 -1
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/inquiry/consts.d.ts +48 -0
- package/build/inquiry/consts.d.ts.map +1 -0
- package/build/inquiry/consts.js +50 -0
- package/build/inquiry/consts.js.map +1 -0
- package/build/inquiry/index.d.ts +4 -0
- package/build/inquiry/index.d.ts.map +1 -0
- package/build/inquiry/index.js +3 -0
- package/build/inquiry/index.js.map +1 -0
- package/build/inquiry/types.d.ts +72 -0
- package/build/inquiry/types.d.ts.map +1 -0
- package/build/inquiry/types.js +2 -0
- package/build/inquiry/types.js.map +1 -0
- package/build/inquiry/utils.d.ts +45 -0
- package/build/inquiry/utils.d.ts.map +1 -0
- package/build/inquiry/utils.js +72 -0
- package/build/inquiry/utils.js.map +1 -0
- package/build/types.d.ts +35 -2
- package/build/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/consts.ts +22 -0
- package/src/delegate/index.ts +1 -0
- package/src/delegate/types.ts +116 -0
- package/src/files/types.ts +11 -0
- package/src/index.ts +2 -0
- package/src/inquiry/consts.ts +52 -0
- package/src/inquiry/index.ts +3 -0
- package/src/inquiry/types.ts +77 -0
- package/src/inquiry/utils.ts +84 -0
- package/src/types.ts +35 -2
- package/tests/inquiry.spec.ts +112 -0
- package/tsconfig.json +3 -1
package/src/types.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
|
/**
|
|
4
5
|
* Free-form observability metadata attached to every model call — forwarded to the
|
|
@@ -32,6 +33,12 @@ export interface ModelConfigPatch {
|
|
|
32
33
|
maxTokensCap?: number
|
|
33
34
|
topP?: number
|
|
34
35
|
disableThinking?: boolean
|
|
36
|
+
/** Total window the model accepts (input + output). Informational / validation only. */
|
|
37
|
+
contextWindow?: number
|
|
38
|
+
/** What the PROVIDER can emit in one request. Hard ceiling for `maxTokens`/`maxTokensCap`. */
|
|
39
|
+
maxOutput?: number
|
|
40
|
+
/** The window is shared between input and output rather than input-only. */
|
|
41
|
+
combinedWindow?: boolean
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
/** A JSON-safe model override: a config alias, or a partial config patch. */
|
|
@@ -49,6 +56,12 @@ export interface ModelPolicy {
|
|
|
49
56
|
roleOverrides?: Partial<Record<ModelRole, ModelRole>>
|
|
50
57
|
/** "Pin a role to a specific model/config" — alias or partial config override. */
|
|
51
58
|
modelOverrides?: Partial<Record<ModelRole, ModelConfigOverride>>
|
|
59
|
+
/**
|
|
60
|
+
* Role resolved by `ExecutionService.utility` for cheap side calls. Defaults to
|
|
61
|
+
* `UTILITY_ROLE`; name another alias when the deployment calls its cheap tier
|
|
62
|
+
* something else. `roleOverrides` still applies on top of whichever one is used.
|
|
63
|
+
*/
|
|
64
|
+
utilityRole?: ModelRole
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
/**
|
|
@@ -129,11 +142,24 @@ export interface ExecutionState {
|
|
|
129
142
|
policy: ModelPolicy
|
|
130
143
|
/** Role + skills for this level; merged downward by `ExecutionService`. */
|
|
131
144
|
prompt?: PromptPolicy
|
|
145
|
+
/**
|
|
146
|
+
* How this run may put a question to a person. Serializable, and deliberately NOT a
|
|
147
|
+
* collaborator: a run that is resumed days later must ask through the same channel, under the
|
|
148
|
+
* same policy, as the one that parked it.
|
|
149
|
+
*/
|
|
150
|
+
inquiry?: InquiryConfig
|
|
132
151
|
}
|
|
133
152
|
|
|
134
|
-
/**
|
|
153
|
+
/**
|
|
154
|
+
* The task level's own fields.
|
|
155
|
+
*
|
|
156
|
+
* `phase`, `completed` and `cursor` are LABELS — for a trace line, a prompt, a log — and never a
|
|
157
|
+
* workflow position. Recoverable position lives on a pipeline run row (`@owlmeans/agent`), which is
|
|
158
|
+
* a single authority; an execution that also claimed to know where a run stood would be a second
|
|
159
|
+
* one, and the two would disagree the first time a step wrote only one of them.
|
|
160
|
+
*/
|
|
135
161
|
export interface TaskExecutionState extends ExecutionState {
|
|
136
|
-
/**
|
|
162
|
+
/** A label for the stage a task considers itself in. Never read back to decide anything. */
|
|
137
163
|
phase?: string
|
|
138
164
|
completed?: string[]
|
|
139
165
|
cursor?: string
|
|
@@ -181,11 +207,18 @@ export interface NullCapture {
|
|
|
181
207
|
tool_calls?: unknown
|
|
182
208
|
} | null
|
|
183
209
|
diagnostics: {
|
|
210
|
+
/** Whatever the provider called it — OpenAI's `finish_reason` or Anthropic's `stop_reason`. */
|
|
184
211
|
finishReason?: string
|
|
185
212
|
inputTokens?: number
|
|
186
213
|
outputTokens?: number
|
|
187
214
|
reasoningTokens?: number
|
|
188
215
|
contentEmpty: boolean
|
|
216
|
+
/**
|
|
217
|
+
* Content arrived, but none of it was text — the shape of an answer that was all reasoning.
|
|
218
|
+
* Distinguishes "spent the budget thinking" from "returned nothing at all", which
|
|
219
|
+
* `contentEmpty` alone cannot.
|
|
220
|
+
*/
|
|
221
|
+
thinkingOnly?: boolean
|
|
189
222
|
hadToolCall: boolean
|
|
190
223
|
}
|
|
191
224
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
answeredWith, capAnswer, CONFIRM_NO, CONFIRM_YES, defaultAnswerFor, DEFAULT_INQUIRY_ANSWER_CHARS,
|
|
4
|
+
INQUIRY_STATE_TEXT_CHARS, InquiryKind, isDeclined, renderInquiry, stateAnswerOf,
|
|
5
|
+
} from '../src/index.js'
|
|
6
|
+
import type { Inquiry, InquiryAnswer } from '../src/index.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The pure half of the inquiry primitive. Every layer that carries an answer — the execution
|
|
10
|
+
* service, the pipeline runner, the `ask_user` tool, the connector — reads it through exactly
|
|
11
|
+
* these functions, so a second reading of "was this answered" is what these specs exist to stop.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const inquiry = (patch: Partial<Inquiry> = {}): Inquiry => ({
|
|
15
|
+
id: 'q1',
|
|
16
|
+
kind: InquiryKind.Choice,
|
|
17
|
+
question: 'Which database does the origin use?',
|
|
18
|
+
options: [
|
|
19
|
+
{ value: 'postgres', label: 'PostgreSQL' },
|
|
20
|
+
{ value: 'mongo', label: 'MongoDB' },
|
|
21
|
+
],
|
|
22
|
+
...patch,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
describe('@owlmeans/llm-common — what an unanswered question assumes', () => {
|
|
26
|
+
test('a question with a default answers itself with it', () => {
|
|
27
|
+
expect(defaultAnswerFor(inquiry({ default: 'postgres' })))
|
|
28
|
+
.toEqual({ inquiryId: 'q1', value: 'postgres' })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('a question without one declines — a decision, never a failure', () => {
|
|
32
|
+
const answer = defaultAnswerFor(inquiry())
|
|
33
|
+
expect(isDeclined(answer)).toBe(true)
|
|
34
|
+
expect(answeredWith(answer)).toBeNull()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('a multiple-choice default travels whole', () => {
|
|
38
|
+
expect(defaultAnswerFor(inquiry({ multiple: true, default: ['postgres', 'mongo'] })).value)
|
|
39
|
+
.toEqual(['postgres', 'mongo'])
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('@owlmeans/llm-common — reading one answer', () => {
|
|
44
|
+
test('the chosen value wins over free text, and the first of a list is the answer', () => {
|
|
45
|
+
expect(answeredWith({ inquiryId: 'q1', value: 'mongo', text: 'or postgres' })).toBe('mongo')
|
|
46
|
+
expect(answeredWith({ inquiryId: 'q1', value: ['mongo', 'redis'] })).toBe('mongo')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('free text answers a question that offered no options', () => {
|
|
50
|
+
expect(answeredWith({ inquiryId: 'q1', text: 'the vendor one' })).toBe('the vendor one')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('an answer that says nothing reads as nothing, declined or not', () => {
|
|
54
|
+
expect(answeredWith({ inquiryId: 'q1' })).toBeNull()
|
|
55
|
+
expect(answeredWith({ inquiryId: 'q1', value: '', text: '' })).toBeNull()
|
|
56
|
+
expect(isDeclined({ inquiryId: 'q1' })).toBe(false)
|
|
57
|
+
expect(isDeclined({ inquiryId: 'q1', declined: true })).toBe(true)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('a confirmation is just its two values', () => {
|
|
61
|
+
expect(answeredWith({ inquiryId: 'q1', value: CONFIRM_YES })).toBe(CONFIRM_YES)
|
|
62
|
+
expect(answeredWith({ inquiryId: 'q1', value: CONFIRM_NO })).toBe(CONFIRM_NO)
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('@owlmeans/llm-common — the one ceiling', () => {
|
|
67
|
+
test('an answer inside the ceiling is left exactly as it came', () => {
|
|
68
|
+
const answer: InquiryAnswer = { inquiryId: 'q1', value: 'mongo', text: 'because of the driver' }
|
|
69
|
+
expect(capAnswer(answer)).toEqual(answer)
|
|
70
|
+
expect(capAnswer(answer).truncated).toBeUndefined()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('a cut is never silent', () => {
|
|
74
|
+
const capped = capAnswer({ inquiryId: 'q1', text: 'x'.repeat(DEFAULT_INQUIRY_ANSWER_CHARS + 5) })
|
|
75
|
+
expect(capped.text).toHaveLength(DEFAULT_INQUIRY_ANSWER_CHARS)
|
|
76
|
+
expect(capped.truncated).toBe(true)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test('an over-long value is reported, never shortened into an option nobody offered', () => {
|
|
80
|
+
const value = ['ok', 'y'.repeat(40)]
|
|
81
|
+
const capped = capAnswer({ inquiryId: 'q1', value }, 10)
|
|
82
|
+
// Cutting prose degrades an answer; cutting an identifier CHANGES it — `answeredWith` would
|
|
83
|
+
// then hand the caller a choice the question never carried.
|
|
84
|
+
expect(capped.value).toEqual(value)
|
|
85
|
+
expect(capped.truncated).toBe(true)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('the state copy keeps the decision whole and cuts only the prose', () => {
|
|
89
|
+
const answer: InquiryAnswer = {
|
|
90
|
+
inquiryId: 'q1', value: 'postgres', text: 'z'.repeat(INQUIRY_STATE_TEXT_CHARS + 100),
|
|
91
|
+
}
|
|
92
|
+
const stored = stateAnswerOf(answer)
|
|
93
|
+
expect(stored.value).toBe('postgres')
|
|
94
|
+
expect(stored.text).toHaveLength(INQUIRY_STATE_TEXT_CHARS)
|
|
95
|
+
expect(stored.truncated).toBe(true)
|
|
96
|
+
// The answer handed back to whoever asked is untouched — only the persisted copy is reduced.
|
|
97
|
+
expect(answer.text).toHaveLength(INQUIRY_STATE_TEXT_CHARS + 100)
|
|
98
|
+
expect(stateAnswerOf({ inquiryId: 'q1', value: 'postgres' }).truncated).toBeUndefined()
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('@owlmeans/llm-common — one line for a trace', () => {
|
|
103
|
+
test('the label names the kind, the question and the choices, on one line', () => {
|
|
104
|
+
expect(renderInquiry(inquiry({ question: 'Which\n database?' })))
|
|
105
|
+
.toBe('[choice] Which database? (postgres | mongo)')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
test('a question with no options renders without an empty bracket', () => {
|
|
109
|
+
expect(renderInquiry({ id: 'q2', kind: InquiryKind.Confirm, question: 'Relocate the origin?' }))
|
|
110
|
+
.toBe('[confirm] Relocate the origin?')
|
|
111
|
+
})
|
|
112
|
+
})
|