ocpipe 0.1.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 +334 -0
- package/agent.ts +176 -0
- package/example/correction.ts +85 -0
- package/example/index.ts +31 -0
- package/example/module.ts +20 -0
- package/example/signature.ts +18 -0
- package/index.ts +127 -0
- package/module.ts +50 -0
- package/package.json +48 -0
- package/parsing.ts +865 -0
- package/pipeline.ts +213 -0
- package/predict.ts +271 -0
- package/signature.ts +97 -0
- package/state.ts +39 -0
- package/testing.ts +180 -0
- package/types.ts +260 -0
package/testing.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSTS SDK testing utilities.
|
|
3
|
+
*
|
|
4
|
+
* Provides mock backends and test helpers for unit testing DSTS components.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { RunAgentOptions, RunAgentResult, FieldConfig } from './types.js'
|
|
8
|
+
|
|
9
|
+
/** MockResponse defines a mock LLM response for testing. */
|
|
10
|
+
export interface MockResponse {
|
|
11
|
+
/** Pattern to match against prompts (string or regex). */
|
|
12
|
+
match?: string | RegExp
|
|
13
|
+
/** The mock response text to return (required unless error is set). */
|
|
14
|
+
response?: string
|
|
15
|
+
/** Optional session ID to return. */
|
|
16
|
+
sessionId?: string
|
|
17
|
+
/** Optional delay in ms to simulate network latency. */
|
|
18
|
+
delay?: number
|
|
19
|
+
/** Optional error to throw instead of returning response. */
|
|
20
|
+
error?: Error
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** MockAgentBackend provides a mock implementation of runAgent for testing. */
|
|
24
|
+
export class MockAgentBackend {
|
|
25
|
+
private responses: MockResponse[] = []
|
|
26
|
+
private calls: RunAgentOptions[] = []
|
|
27
|
+
private defaultSessionId = 'mock-session-001'
|
|
28
|
+
private callCount = 0
|
|
29
|
+
|
|
30
|
+
/** addResponse adds a mock response to the queue. */
|
|
31
|
+
addResponse(response: MockResponse): this {
|
|
32
|
+
this.responses.push(response)
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** addJsonResponse adds a mock JSON response. */
|
|
37
|
+
addJsonResponse(data: Record<string, unknown>, options?: Partial<MockResponse>): this {
|
|
38
|
+
return this.addResponse({
|
|
39
|
+
response: JSON.stringify(data, null, 2),
|
|
40
|
+
...options,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** getCalls returns all recorded calls. */
|
|
45
|
+
getCalls(): RunAgentOptions[] {
|
|
46
|
+
return this.calls
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** getLastCall returns the most recent call. */
|
|
50
|
+
getLastCall(): RunAgentOptions | undefined {
|
|
51
|
+
return this.calls[this.calls.length - 1]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** getCallCount returns the number of calls made. */
|
|
55
|
+
getCallCount(): number {
|
|
56
|
+
return this.callCount
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** reset clears all responses and calls. */
|
|
60
|
+
reset(): this {
|
|
61
|
+
this.responses = []
|
|
62
|
+
this.calls = []
|
|
63
|
+
this.callCount = 0
|
|
64
|
+
return this
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** runAgent is the mock implementation. */
|
|
68
|
+
async runAgent(options: RunAgentOptions): Promise<RunAgentResult> {
|
|
69
|
+
this.calls.push(options)
|
|
70
|
+
this.callCount++
|
|
71
|
+
|
|
72
|
+
// Find matching response and consume it
|
|
73
|
+
let responseIndex = -1
|
|
74
|
+
let response: MockResponse | undefined
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < this.responses.length; i++) {
|
|
77
|
+
const r = this.responses[i]
|
|
78
|
+
if (!r) continue
|
|
79
|
+
|
|
80
|
+
if (!r.match) {
|
|
81
|
+
response = r
|
|
82
|
+
responseIndex = i
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
if (typeof r.match === 'string' && options.prompt.includes(r.match)) {
|
|
86
|
+
response = r
|
|
87
|
+
responseIndex = i
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
if (r.match instanceof RegExp && r.match.test(options.prompt)) {
|
|
91
|
+
response = r
|
|
92
|
+
responseIndex = i
|
|
93
|
+
break
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Consume the response (remove from queue)
|
|
98
|
+
if (responseIndex >= 0) {
|
|
99
|
+
this.responses.splice(responseIndex, 1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!response) {
|
|
103
|
+
// Return default empty JSON response
|
|
104
|
+
response = { response: '{}' }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Simulate delay
|
|
108
|
+
if (response.delay) {
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, response.delay))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Throw error if specified
|
|
113
|
+
if (response.error) {
|
|
114
|
+
throw response.error
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
text: response.response ?? '',
|
|
119
|
+
sessionId: response.sessionId ?? options.sessionId ?? this.defaultSessionId,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** createRunner returns a bound runAgent function for use with vi.mock. */
|
|
124
|
+
createRunner(): (options: RunAgentOptions) => Promise<RunAgentResult> {
|
|
125
|
+
return this.runAgent.bind(this)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** createMockContext creates a test execution context. */
|
|
130
|
+
export function createMockContext(overrides?: Partial<{
|
|
131
|
+
sessionId: string
|
|
132
|
+
defaultModel: { providerID: string; modelID: string }
|
|
133
|
+
defaultAgent: string
|
|
134
|
+
timeoutSec: number
|
|
135
|
+
}>) {
|
|
136
|
+
return {
|
|
137
|
+
sessionId: overrides?.sessionId,
|
|
138
|
+
defaultModel: overrides?.defaultModel ?? {
|
|
139
|
+
providerID: 'github-copilot',
|
|
140
|
+
modelID: 'grok-code-fast-1',
|
|
141
|
+
},
|
|
142
|
+
defaultAgent: overrides?.defaultAgent ?? 'general',
|
|
143
|
+
timeoutSec: overrides?.timeoutSec ?? 60,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** generateMockOutputs creates mock output data based on a schema. */
|
|
148
|
+
export function generateMockOutputs(schema: Record<string, FieldConfig>): Record<string, unknown> {
|
|
149
|
+
const result: Record<string, unknown> = {}
|
|
150
|
+
for (const [name, config] of Object.entries(schema)) {
|
|
151
|
+
// Use constructor name for type detection (works across zod versions)
|
|
152
|
+
const typeName = config.type.constructor.name
|
|
153
|
+
|
|
154
|
+
switch (typeName) {
|
|
155
|
+
case 'ZodString':
|
|
156
|
+
result[name] = `mock_${name}`
|
|
157
|
+
break
|
|
158
|
+
case 'ZodNumber':
|
|
159
|
+
result[name] = 42
|
|
160
|
+
break
|
|
161
|
+
case 'ZodBoolean':
|
|
162
|
+
result[name] = true
|
|
163
|
+
break
|
|
164
|
+
case 'ZodArray':
|
|
165
|
+
result[name] = []
|
|
166
|
+
break
|
|
167
|
+
case 'ZodObject':
|
|
168
|
+
result[name] = {}
|
|
169
|
+
break
|
|
170
|
+
case 'ZodEnum':
|
|
171
|
+
// Get first enum value via options property
|
|
172
|
+
const enumType = config.type as { options?: readonly string[] }
|
|
173
|
+
result[name] = enumType.options?.[0] ?? 'unknown'
|
|
174
|
+
break
|
|
175
|
+
default:
|
|
176
|
+
result[name] = null
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result
|
|
180
|
+
}
|
package/types.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSTS SDK shared types.
|
|
3
|
+
*
|
|
4
|
+
* Core type definitions for the Declarative Self-Improving TypeScript SDK.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { z } from 'zod'
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Model Configuration
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/** Model configuration for OpenCode. */
|
|
14
|
+
export interface ModelConfig {
|
|
15
|
+
providerID: string
|
|
16
|
+
modelID: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Execution Context
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/** Execution context passed through pipeline execution. */
|
|
24
|
+
export interface ExecutionContext {
|
|
25
|
+
/** Current OpenCode session ID (for continuity). */
|
|
26
|
+
sessionId?: string
|
|
27
|
+
/** Default model for predictions. */
|
|
28
|
+
defaultModel: ModelConfig
|
|
29
|
+
/** Default agent for predictions. */
|
|
30
|
+
defaultAgent: string
|
|
31
|
+
/** Timeout in seconds for agent calls. */
|
|
32
|
+
timeoutSec: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Step Results
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
/** Result from a single pipeline step. */
|
|
40
|
+
export interface StepResult<T> {
|
|
41
|
+
/** Parsed and validated output data. */
|
|
42
|
+
data: T
|
|
43
|
+
/** Step name for logging. */
|
|
44
|
+
stepName: string
|
|
45
|
+
/** Execution duration in milliseconds. */
|
|
46
|
+
duration: number
|
|
47
|
+
/** OpenCode session ID used. */
|
|
48
|
+
sessionId: string
|
|
49
|
+
/** Model used for this step. */
|
|
50
|
+
model: ModelConfig
|
|
51
|
+
/** Which retry attempt succeeded (1-based). */
|
|
52
|
+
attempt: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Record of a completed step for checkpointing. */
|
|
56
|
+
export interface StepRecord {
|
|
57
|
+
stepName: string
|
|
58
|
+
timestamp: string
|
|
59
|
+
result: StepResult<unknown>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Record of a sub-pipeline execution. */
|
|
63
|
+
export interface SubPipelineRecord {
|
|
64
|
+
name: string
|
|
65
|
+
sessionId: string
|
|
66
|
+
timestamp: string
|
|
67
|
+
state: BaseState
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// State
|
|
72
|
+
// ============================================================================
|
|
73
|
+
|
|
74
|
+
/** Base state interface for all pipeline states. */
|
|
75
|
+
export interface BaseState {
|
|
76
|
+
/** Unique ID for this pipeline run. */
|
|
77
|
+
sessionId: string
|
|
78
|
+
/** ISO timestamp when pipeline started. */
|
|
79
|
+
startedAt: string
|
|
80
|
+
/** Current OpenCode session ID (for continuity). */
|
|
81
|
+
opencodeSessionId?: string
|
|
82
|
+
/** Current phase name (for resume). */
|
|
83
|
+
phase: string
|
|
84
|
+
/** All completed steps. */
|
|
85
|
+
steps: StepRecord[]
|
|
86
|
+
/** References to sub-pipelines. */
|
|
87
|
+
subPipelines: SubPipelineRecord[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ============================================================================
|
|
91
|
+
// Predict Results
|
|
92
|
+
// ============================================================================
|
|
93
|
+
|
|
94
|
+
/** Result from a Predict.execute() call. */
|
|
95
|
+
export interface PredictResult<T> {
|
|
96
|
+
/** Parsed and validated output data. */
|
|
97
|
+
data: T
|
|
98
|
+
/** Raw response text from the LLM. */
|
|
99
|
+
raw: string
|
|
100
|
+
/** OpenCode session ID. */
|
|
101
|
+
sessionId: string
|
|
102
|
+
/** Execution duration in milliseconds. */
|
|
103
|
+
duration: number
|
|
104
|
+
/** Model used. */
|
|
105
|
+
model: ModelConfig
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ============================================================================
|
|
109
|
+
// Signature Types
|
|
110
|
+
// ============================================================================
|
|
111
|
+
|
|
112
|
+
/** Configuration for a signature field. */
|
|
113
|
+
export interface FieldConfig<T extends z.ZodType = z.ZodType> {
|
|
114
|
+
/** Zod type for validation. */
|
|
115
|
+
type: T
|
|
116
|
+
/** Description of the field (used in prompt generation). */
|
|
117
|
+
desc?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Signature definition with typed inputs and outputs. */
|
|
121
|
+
export interface SignatureDef<
|
|
122
|
+
I extends Record<string, FieldConfig>,
|
|
123
|
+
O extends Record<string, FieldConfig>,
|
|
124
|
+
> {
|
|
125
|
+
/** Documentation/instruction for the LLM. */
|
|
126
|
+
doc: string
|
|
127
|
+
/** Input field definitions. */
|
|
128
|
+
inputs: I
|
|
129
|
+
/** Output field definitions. */
|
|
130
|
+
outputs: O
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Infer the input type from a signature definition. */
|
|
134
|
+
export type InferInputs<S extends SignatureDef<any, any>> =
|
|
135
|
+
S extends SignatureDef<infer I, any>
|
|
136
|
+
? { [K in keyof I]: z.infer<I[K]['type']> }
|
|
137
|
+
: never
|
|
138
|
+
|
|
139
|
+
/** Infer the output type from a signature definition. */
|
|
140
|
+
export type InferOutputs<S extends SignatureDef<any, any>> =
|
|
141
|
+
S extends SignatureDef<any, infer O>
|
|
142
|
+
? { [K in keyof O]: z.infer<O[K]['type']> }
|
|
143
|
+
: never
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// Retry Configuration
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
/** Retry configuration for pipeline steps. */
|
|
150
|
+
export interface RetryConfig {
|
|
151
|
+
/** Maximum number of attempts. */
|
|
152
|
+
maxAttempts: number
|
|
153
|
+
/** Whether to retry on parse errors (JSON failures). */
|
|
154
|
+
onParseError?: boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Schema Correction
|
|
159
|
+
// ============================================================================
|
|
160
|
+
|
|
161
|
+
/** Correction method for fixing schema validation errors. */
|
|
162
|
+
export type CorrectionMethod = 'json-patch' | 'jq'
|
|
163
|
+
|
|
164
|
+
/** Configuration for automatic schema correction on parse failures. */
|
|
165
|
+
export interface CorrectionConfig {
|
|
166
|
+
/** Correction method to use (default: 'json-patch'). */
|
|
167
|
+
method?: CorrectionMethod
|
|
168
|
+
/** Use a different model for corrections (default: same model, same session). */
|
|
169
|
+
model?: ModelConfig
|
|
170
|
+
/** Maximum number of fields to attempt correcting per round (default: 5). */
|
|
171
|
+
maxFields?: number
|
|
172
|
+
/** Maximum number of correction rounds before giving up (default: 3). */
|
|
173
|
+
maxRounds?: number
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** A field-level error from schema validation. */
|
|
177
|
+
export interface FieldError {
|
|
178
|
+
/** The field path that failed (e.g., "issues.0.issue_type"). */
|
|
179
|
+
path: string
|
|
180
|
+
/** Human-readable error message. */
|
|
181
|
+
message: string
|
|
182
|
+
/** Expected type description. */
|
|
183
|
+
expectedType: string
|
|
184
|
+
/** If a similar field was found, its name. */
|
|
185
|
+
foundField?: string
|
|
186
|
+
/** The value found at the wrong field. */
|
|
187
|
+
foundValue?: unknown
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Result from trying to parse a response. */
|
|
191
|
+
export interface TryParseResult<T> {
|
|
192
|
+
/** Whether parsing succeeded. */
|
|
193
|
+
ok: boolean
|
|
194
|
+
/** Parsed data if successful. */
|
|
195
|
+
data?: T
|
|
196
|
+
/** Field errors if validation failed. */
|
|
197
|
+
errors?: FieldError[]
|
|
198
|
+
/** The extracted JSON object (even if validation failed). */
|
|
199
|
+
json?: Record<string, unknown>
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Pipeline Configuration
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
/** Configuration for a pipeline. */
|
|
207
|
+
export interface PipelineConfig {
|
|
208
|
+
/** Pipeline name (used in checkpoints). */
|
|
209
|
+
name: string
|
|
210
|
+
/** Default model for predictions. */
|
|
211
|
+
defaultModel: ModelConfig
|
|
212
|
+
/** Default agent for predictions. */
|
|
213
|
+
defaultAgent: string
|
|
214
|
+
/** Directory for checkpoint files. */
|
|
215
|
+
checkpointDir: string
|
|
216
|
+
/** Directory for log files. */
|
|
217
|
+
logDir: string
|
|
218
|
+
/** Default retry configuration. */
|
|
219
|
+
retry?: RetryConfig
|
|
220
|
+
/** Default timeout in seconds. */
|
|
221
|
+
timeoutSec?: number
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Options for running a pipeline step. */
|
|
225
|
+
export interface RunOptions {
|
|
226
|
+
/** Step name for logging (defaults to module class name). */
|
|
227
|
+
name?: string
|
|
228
|
+
/** Override model for this step. */
|
|
229
|
+
model?: ModelConfig
|
|
230
|
+
/** Start fresh session (don't reuse existing). */
|
|
231
|
+
newSession?: boolean
|
|
232
|
+
/** Override retry config for this step. */
|
|
233
|
+
retry?: RetryConfig
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// Agent Types
|
|
238
|
+
// ============================================================================
|
|
239
|
+
|
|
240
|
+
/** Options for running an OpenCode agent. */
|
|
241
|
+
export interface RunAgentOptions {
|
|
242
|
+
/** The prompt to send to the agent. */
|
|
243
|
+
prompt: string
|
|
244
|
+
/** Agent type (e.g., "journey-creator", "explore", "general"). */
|
|
245
|
+
agent: string
|
|
246
|
+
/** Model to use. */
|
|
247
|
+
model: ModelConfig
|
|
248
|
+
/** Existing session ID to continue. */
|
|
249
|
+
sessionId?: string
|
|
250
|
+
/** Timeout in seconds. */
|
|
251
|
+
timeoutSec?: number
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Result from running an OpenCode agent. */
|
|
255
|
+
export interface RunAgentResult {
|
|
256
|
+
/** The text response from the agent. */
|
|
257
|
+
text: string
|
|
258
|
+
/** Session ID (for continuing the conversation). */
|
|
259
|
+
sessionId: string
|
|
260
|
+
}
|