@funkai/agents 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/.generated/req.txt +1 -0
- package/.turbo/turbo-build.log +21 -0
- package/.turbo/turbo-test$colon$coverage.log +109 -0
- package/.turbo/turbo-test.log +141 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +16 -0
- package/ISSUES.md +540 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/banner.svg +97 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/core/agents/base/agent.ts.html +1705 -0
- package/coverage/lcov-report/core/agents/base/index.html +146 -0
- package/coverage/lcov-report/core/agents/base/output.ts.html +256 -0
- package/coverage/lcov-report/core/agents/base/utils.ts.html +694 -0
- package/coverage/lcov-report/core/agents/flow/engine.ts.html +928 -0
- package/coverage/lcov-report/core/agents/flow/flow-agent.ts.html +1462 -0
- package/coverage/lcov-report/core/agents/flow/index.html +146 -0
- package/coverage/lcov-report/core/agents/flow/messages.ts.html +508 -0
- package/coverage/lcov-report/core/agents/flow/steps/factory.ts.html +1975 -0
- package/coverage/lcov-report/core/agents/flow/steps/index.html +116 -0
- package/coverage/lcov-report/core/index.html +131 -0
- package/coverage/lcov-report/core/logger.ts.html +541 -0
- package/coverage/lcov-report/core/models/providers/index.html +116 -0
- package/coverage/lcov-report/core/models/providers/openai.ts.html +337 -0
- package/coverage/lcov-report/core/provider/index.html +131 -0
- package/coverage/lcov-report/core/provider/provider.ts.html +346 -0
- package/coverage/lcov-report/core/provider/usage.ts.html +376 -0
- package/coverage/lcov-report/core/tool.ts.html +577 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +221 -0
- package/coverage/lcov-report/lib/hooks.ts.html +262 -0
- package/coverage/lcov-report/lib/index.html +161 -0
- package/coverage/lcov-report/lib/middleware.ts.html +274 -0
- package/coverage/lcov-report/lib/runnable.ts.html +151 -0
- package/coverage/lcov-report/lib/trace.ts.html +520 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov-report/utils/attempt.ts.html +199 -0
- package/coverage/lcov-report/utils/error.ts.html +421 -0
- package/coverage/lcov-report/utils/index.html +176 -0
- package/coverage/lcov-report/utils/resolve.ts.html +208 -0
- package/coverage/lcov-report/utils/result.ts.html +538 -0
- package/coverage/lcov-report/utils/zod.ts.html +178 -0
- package/coverage/lcov.info +1566 -0
- package/dist/index.d.mts +2883 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2312 -0
- package/dist/index.mjs.map +1 -0
- package/docs/core/agent.md +231 -0
- package/docs/core/hooks.md +95 -0
- package/docs/core/overview.md +87 -0
- package/docs/core/step.md +279 -0
- package/docs/core/tools.md +98 -0
- package/docs/core/workflow.md +235 -0
- package/docs/guides/create-agent.md +224 -0
- package/docs/guides/create-tool.md +137 -0
- package/docs/guides/create-workflow.md +374 -0
- package/docs/overview.md +244 -0
- package/docs/provider/models.md +55 -0
- package/docs/provider/overview.md +106 -0
- package/docs/provider/usage.md +100 -0
- package/docs/research/experimental-context.md +167 -0
- package/docs/research/gap-analysis.md +86 -0
- package/docs/research/prepare-step-and-active-tools.md +138 -0
- package/docs/research/sub-agent-model.md +249 -0
- package/docs/troubleshooting.md +60 -0
- package/logo.svg +17 -0
- package/models.config.json +18 -0
- package/package.json +60 -0
- package/scripts/generate-models.ts +324 -0
- package/src/core/agents/base/agent.test.ts +1522 -0
- package/src/core/agents/base/agent.ts +547 -0
- package/src/core/agents/base/output.test.ts +93 -0
- package/src/core/agents/base/output.ts +57 -0
- package/src/core/agents/base/types.test-d.ts +69 -0
- package/src/core/agents/base/types.ts +503 -0
- package/src/core/agents/base/utils.test.ts +397 -0
- package/src/core/agents/base/utils.ts +197 -0
- package/src/core/agents/flow/engine.test.ts +452 -0
- package/src/core/agents/flow/engine.ts +281 -0
- package/src/core/agents/flow/flow-agent.test.ts +1027 -0
- package/src/core/agents/flow/flow-agent.ts +473 -0
- package/src/core/agents/flow/messages.test.ts +198 -0
- package/src/core/agents/flow/messages.ts +141 -0
- package/src/core/agents/flow/steps/agent.test.ts +280 -0
- package/src/core/agents/flow/steps/agent.ts +87 -0
- package/src/core/agents/flow/steps/all.test.ts +300 -0
- package/src/core/agents/flow/steps/all.ts +73 -0
- package/src/core/agents/flow/steps/builder.ts +124 -0
- package/src/core/agents/flow/steps/each.test.ts +257 -0
- package/src/core/agents/flow/steps/each.ts +61 -0
- package/src/core/agents/flow/steps/factory.test-d.ts +50 -0
- package/src/core/agents/flow/steps/factory.test.ts +1025 -0
- package/src/core/agents/flow/steps/factory.ts +645 -0
- package/src/core/agents/flow/steps/map.test.ts +273 -0
- package/src/core/agents/flow/steps/map.ts +75 -0
- package/src/core/agents/flow/steps/race.test.ts +290 -0
- package/src/core/agents/flow/steps/race.ts +59 -0
- package/src/core/agents/flow/steps/reduce.test.ts +310 -0
- package/src/core/agents/flow/steps/reduce.ts +73 -0
- package/src/core/agents/flow/steps/result.ts +27 -0
- package/src/core/agents/flow/steps/step.test.ts +402 -0
- package/src/core/agents/flow/steps/step.ts +51 -0
- package/src/core/agents/flow/steps/while.test.ts +283 -0
- package/src/core/agents/flow/steps/while.ts +75 -0
- package/src/core/agents/flow/types.ts +348 -0
- package/src/core/logger.test.ts +163 -0
- package/src/core/logger.ts +152 -0
- package/src/core/models/index.test.ts +137 -0
- package/src/core/models/index.ts +152 -0
- package/src/core/models/providers/openai.ts +84 -0
- package/src/core/provider/provider.test.ts +128 -0
- package/src/core/provider/provider.ts +99 -0
- package/src/core/provider/types.ts +98 -0
- package/src/core/provider/usage.test.ts +304 -0
- package/src/core/provider/usage.ts +97 -0
- package/src/core/tool.test.ts +65 -0
- package/src/core/tool.ts +164 -0
- package/src/core/types.ts +66 -0
- package/src/index.ts +95 -0
- package/src/lib/context.test.ts +86 -0
- package/src/lib/context.ts +49 -0
- package/src/lib/hooks.test.ts +102 -0
- package/src/lib/hooks.ts +59 -0
- package/src/lib/middleware.test.ts +122 -0
- package/src/lib/middleware.ts +63 -0
- package/src/lib/runnable.test.ts +41 -0
- package/src/lib/runnable.ts +22 -0
- package/src/lib/trace.test.ts +291 -0
- package/src/lib/trace.ts +145 -0
- package/src/models/index.ts +123 -0
- package/src/models/providers/index.ts +15 -0
- package/src/models/providers/openai.ts +84 -0
- package/src/testing/context.ts +32 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/logger.ts +19 -0
- package/src/utils/attempt.test.ts +127 -0
- package/src/utils/attempt.ts +38 -0
- package/src/utils/error.test.ts +179 -0
- package/src/utils/error.ts +112 -0
- package/src/utils/resolve.test.ts +38 -0
- package/src/utils/resolve.ts +41 -0
- package/src/utils/result.test.ts +79 -0
- package/src/utils/result.ts +151 -0
- package/src/utils/zod.test.ts +69 -0
- package/src/utils/zod.ts +31 -0
- package/tsconfig.json +25 -0
- package/tsdown.config.ts +15 -0
- package/vitest.config.ts +46 -0
package/src/lib/trace.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { match, P } from "ts-pattern";
|
|
2
|
+
|
|
3
|
+
import type { TokenUsage } from "@/core/provider/types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Known trace operation types.
|
|
7
|
+
*
|
|
8
|
+
* Each `$` method registers a specific operation type in the execution
|
|
9
|
+
* trace. This discriminant allows consumers to filter or group trace
|
|
10
|
+
* entries by kind.
|
|
11
|
+
*/
|
|
12
|
+
export type OperationType = "step" | "agent" | "map" | "each" | "reduce" | "while" | "all" | "race";
|
|
13
|
+
|
|
14
|
+
/** @deprecated Use `OperationType` instead. */
|
|
15
|
+
export type TraceType = OperationType;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A single entry in the execution trace.
|
|
19
|
+
*
|
|
20
|
+
* Every tracked `$` operation produces a TraceEntry. Nested operations
|
|
21
|
+
* (e.g. agent calls inside a map iteration) appear as children,
|
|
22
|
+
* forming a tree that represents the full execution graph.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
* Part of the internal execution context. Exposed on
|
|
26
|
+
* `WorkflowResult.trace` for observability but not directly
|
|
27
|
+
* constructed by user code.
|
|
28
|
+
*/
|
|
29
|
+
export interface TraceEntry {
|
|
30
|
+
/**
|
|
31
|
+
* Unique id of this operation.
|
|
32
|
+
*
|
|
33
|
+
* Corresponds to the `id` field from the `$` config that
|
|
34
|
+
* produced this entry.
|
|
35
|
+
*/
|
|
36
|
+
id: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* What kind of operation produced this entry.
|
|
40
|
+
*
|
|
41
|
+
* Discriminant for filtering or grouping trace entries.
|
|
42
|
+
*/
|
|
43
|
+
type: OperationType;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Input snapshot.
|
|
47
|
+
*
|
|
48
|
+
* Captured when the operation starts. May be `undefined` for
|
|
49
|
+
* operations that have no meaningful input (e.g. `$.all`).
|
|
50
|
+
*/
|
|
51
|
+
input?: unknown;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Output snapshot.
|
|
55
|
+
*
|
|
56
|
+
* Captured when the operation completes successfully. `undefined`
|
|
57
|
+
* if the operation is still running or failed.
|
|
58
|
+
*/
|
|
59
|
+
output?: unknown;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Start time in Unix milliseconds.
|
|
63
|
+
*
|
|
64
|
+
* Set when the operation begins execution.
|
|
65
|
+
*/
|
|
66
|
+
startedAt: number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* End time in Unix milliseconds.
|
|
70
|
+
*
|
|
71
|
+
* Set when the operation completes (success or failure).
|
|
72
|
+
* `undefined` while the operation is still running.
|
|
73
|
+
*/
|
|
74
|
+
finishedAt?: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Error instance if the operation failed.
|
|
78
|
+
*
|
|
79
|
+
* `undefined` on success or while still running.
|
|
80
|
+
*/
|
|
81
|
+
error?: Error;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Token usage from this operation.
|
|
85
|
+
*
|
|
86
|
+
* Populated for `agent` type entries that complete successfully.
|
|
87
|
+
* `undefined` for non-agent steps or failed operations.
|
|
88
|
+
*/
|
|
89
|
+
usage?: TokenUsage;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Nested trace entries for child operations.
|
|
93
|
+
*
|
|
94
|
+
* Present when this operation spawns sub-operations
|
|
95
|
+
* (e.g. individual iterations inside `$.map`, or nested
|
|
96
|
+
* `$.step` calls inside a step's `execute` callback).
|
|
97
|
+
*/
|
|
98
|
+
children?: readonly TraceEntry[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Recursively collect all {@link TokenUsage} values from a trace tree.
|
|
103
|
+
*
|
|
104
|
+
* Walks every entry (including nested children) and returns a flat
|
|
105
|
+
* array of usage objects. Entries without usage are skipped.
|
|
106
|
+
*
|
|
107
|
+
* @param trace - The trace array to collect from.
|
|
108
|
+
* @returns Flat array of {@link TokenUsage} values found in the tree.
|
|
109
|
+
*/
|
|
110
|
+
export function collectUsages(trace: readonly TraceEntry[]): TokenUsage[] {
|
|
111
|
+
return trace.flatMap((entry) => {
|
|
112
|
+
const usages: TokenUsage[] = match(entry.usage)
|
|
113
|
+
.with(P.nonNullable, (u) => [u])
|
|
114
|
+
.otherwise(() => []);
|
|
115
|
+
if (entry.children != null && entry.children.length > 0) {
|
|
116
|
+
return [...usages, ...collectUsages(entry.children)];
|
|
117
|
+
}
|
|
118
|
+
return usages;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Recursively deep-clone and freeze a trace array.
|
|
124
|
+
*
|
|
125
|
+
* Returns a structurally identical tree that is `Object.freeze`d at
|
|
126
|
+
* every level, preventing post-run mutation of the result trace.
|
|
127
|
+
*
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
export function snapshotTrace(trace: readonly TraceEntry[]): readonly TraceEntry[] {
|
|
131
|
+
return Object.freeze(
|
|
132
|
+
trace.map((entry) => {
|
|
133
|
+
const children = match(entry.children)
|
|
134
|
+
.with(P.nonNullable, (c) => snapshotTrace(c))
|
|
135
|
+
.otherwise(() => undefined);
|
|
136
|
+
const childSpread = match(children)
|
|
137
|
+
.with(P.nonNullable, (c) => ({ children: c }))
|
|
138
|
+
.otherwise(() => ({}));
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
...entry,
|
|
141
|
+
...childSpread,
|
|
142
|
+
});
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// ──────────────────────────────────────────────────────────────
|
|
2
|
+
// █████╗ ██████╗ ███████╗███╗ ██╗████████╗███████╗
|
|
3
|
+
// ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
|
4
|
+
// ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ███████╗
|
|
5
|
+
// ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
|
6
|
+
// ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████║
|
|
7
|
+
// ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
|
8
|
+
//
|
|
9
|
+
// AUTO-GENERATED — DO NOT EDIT
|
|
10
|
+
// Update: pnpm --filter=@pkg/agent-sdk generate:models
|
|
11
|
+
// ──────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
import { match } from "ts-pattern";
|
|
14
|
+
|
|
15
|
+
import { MODELS as GENERATED_MODELS } from "./providers/index.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Supported OpenRouter model identifiers, derived from the generated {@link MODELS} array.
|
|
19
|
+
*/
|
|
20
|
+
export type OpenRouterLanguageModelId = (typeof GENERATED_MODELS)[number]["id"];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Model category for classification and filtering.
|
|
24
|
+
*/
|
|
25
|
+
export type ModelCategory = "chat" | "coding" | "reasoning";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Per-model pricing in USD per token.
|
|
29
|
+
*
|
|
30
|
+
* Field names match the OpenRouter API convention. All values are
|
|
31
|
+
* per-token (or per-unit) rates as numbers. Optional fields are
|
|
32
|
+
* omitted when the provider does not support them.
|
|
33
|
+
*/
|
|
34
|
+
export interface ModelPricing {
|
|
35
|
+
/** Cost per input (prompt) token. */
|
|
36
|
+
prompt: number;
|
|
37
|
+
|
|
38
|
+
/** Cost per output (completion) token. */
|
|
39
|
+
completion: number;
|
|
40
|
+
|
|
41
|
+
/** Cost per cached input token (read). */
|
|
42
|
+
inputCacheRead?: number;
|
|
43
|
+
|
|
44
|
+
/** Cost per cached input token (write). */
|
|
45
|
+
inputCacheWrite?: number;
|
|
46
|
+
|
|
47
|
+
/** Cost per web search request. */
|
|
48
|
+
webSearch?: number;
|
|
49
|
+
|
|
50
|
+
/** Cost per internal reasoning token. */
|
|
51
|
+
internalReasoning?: number;
|
|
52
|
+
|
|
53
|
+
/** Cost per image input token. */
|
|
54
|
+
image?: number;
|
|
55
|
+
|
|
56
|
+
/** Cost per audio input second. */
|
|
57
|
+
audio?: number;
|
|
58
|
+
|
|
59
|
+
/** Cost per audio output second. */
|
|
60
|
+
audioOutput?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Model definition with metadata and pricing.
|
|
65
|
+
*/
|
|
66
|
+
export interface ModelDefinition {
|
|
67
|
+
/** OpenRouter model identifier (e.g. `"openai/gpt-5.2-codex"`). */
|
|
68
|
+
id: string;
|
|
69
|
+
|
|
70
|
+
/** Model category for classification. */
|
|
71
|
+
category: ModelCategory;
|
|
72
|
+
|
|
73
|
+
/** Token pricing rates. */
|
|
74
|
+
pricing: ModelPricing;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Supported OpenRouter models with pricing data.
|
|
79
|
+
*/
|
|
80
|
+
export const MODELS = GENERATED_MODELS satisfies readonly ModelDefinition[];
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Look up a model definition by its identifier.
|
|
84
|
+
*
|
|
85
|
+
* @param id - The model identifier to look up.
|
|
86
|
+
* @returns The matching model definition.
|
|
87
|
+
* @throws {Error} If no model matches the given ID.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* const m = model('openai/gpt-5.2-codex')
|
|
92
|
+
* console.log(m.pricing.prompt) // 0.00000175
|
|
93
|
+
* console.log(m.category) // 'coding'
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function model(id: OpenRouterLanguageModelId): ModelDefinition {
|
|
97
|
+
const found = MODELS.find((m) => m.id === id);
|
|
98
|
+
if (!found) {
|
|
99
|
+
throw new Error(`Unknown model: ${id}`);
|
|
100
|
+
}
|
|
101
|
+
return found;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Return supported model definitions, optionally filtered.
|
|
106
|
+
*
|
|
107
|
+
* @param filter - Optional predicate to filter models.
|
|
108
|
+
* @returns A readonly array of matching model definitions.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const all = models()
|
|
113
|
+
* const reasoning = models((m) => m.category === 'reasoning')
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export function models(filter?: (m: ModelDefinition) => boolean): readonly ModelDefinition[] {
|
|
117
|
+
return match(filter)
|
|
118
|
+
.when(
|
|
119
|
+
(f): f is (m: ModelDefinition) => boolean => f != null,
|
|
120
|
+
(f) => MODELS.filter(f),
|
|
121
|
+
)
|
|
122
|
+
.otherwise(() => MODELS);
|
|
123
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// ──────────────────────────────────────────────────────────────
|
|
2
|
+
// █████╗ ██████╗ ███████╗███╗ ██╗████████╗███████╗
|
|
3
|
+
// ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
|
4
|
+
// ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ███████╗
|
|
5
|
+
// ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
|
6
|
+
// ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████║
|
|
7
|
+
// ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
|
8
|
+
//
|
|
9
|
+
// AUTO-GENERATED — DO NOT EDIT
|
|
10
|
+
// Update: pnpm --filter=@pkg/agent-sdk generate:models
|
|
11
|
+
// ──────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
import { OPENAI_MODELS } from "./openai.js";
|
|
14
|
+
|
|
15
|
+
export const MODELS = [...OPENAI_MODELS] as const;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// ──────────────────────────────────────────────────────────────
|
|
2
|
+
// █████╗ ██████╗ ███████╗███╗ ██╗████████╗███████╗
|
|
3
|
+
// ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██╔════╝
|
|
4
|
+
// ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ███████╗
|
|
5
|
+
// ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ╚════██║
|
|
6
|
+
// ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████║
|
|
7
|
+
// ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝
|
|
8
|
+
//
|
|
9
|
+
// AUTO-GENERATED — DO NOT EDIT
|
|
10
|
+
// Update: pnpm --filter=@pkg/agent-sdk generate:models
|
|
11
|
+
// ──────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const OPENAI_MODELS = [
|
|
14
|
+
{
|
|
15
|
+
id: "openai/gpt-5.2-codex",
|
|
16
|
+
category: "coding",
|
|
17
|
+
pricing: { prompt: 0.00000175, completion: 0.000014, inputCacheRead: 1.75e-7, webSearch: 0.01 },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "openai/gpt-5.2",
|
|
21
|
+
category: "chat",
|
|
22
|
+
pricing: { prompt: 0.00000175, completion: 0.000014, inputCacheRead: 1.75e-7, webSearch: 0.01 },
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "openai/gpt-5.1",
|
|
26
|
+
category: "chat",
|
|
27
|
+
pricing: { prompt: 0.00000125, completion: 0.00001, inputCacheRead: 1.25e-7, webSearch: 0.01 },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "openai/gpt-5",
|
|
31
|
+
category: "chat",
|
|
32
|
+
pricing: { prompt: 0.00000125, completion: 0.00001, inputCacheRead: 1.25e-7, webSearch: 0.01 },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "openai/gpt-5-mini",
|
|
36
|
+
category: "chat",
|
|
37
|
+
pricing: { prompt: 2.5e-7, completion: 0.000002, inputCacheRead: 2.5e-8, webSearch: 0.01 },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "openai/gpt-5-nano",
|
|
41
|
+
category: "chat",
|
|
42
|
+
pricing: { prompt: 5e-8, completion: 4e-7, inputCacheRead: 5e-9, webSearch: 0.01 },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "openai/gpt-4.1",
|
|
46
|
+
category: "chat",
|
|
47
|
+
pricing: { prompt: 0.000002, completion: 0.000008, inputCacheRead: 5e-7, webSearch: 0.01 },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "openai/gpt-4.1-mini",
|
|
51
|
+
category: "chat",
|
|
52
|
+
pricing: { prompt: 4e-7, completion: 0.0000016, inputCacheRead: 1e-7, webSearch: 0.01 },
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "openai/gpt-4.1-nano",
|
|
56
|
+
category: "chat",
|
|
57
|
+
pricing: { prompt: 1e-7, completion: 4e-7, inputCacheRead: 2.5e-8, webSearch: 0.01 },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "openai/gpt-4o",
|
|
61
|
+
category: "chat",
|
|
62
|
+
pricing: { prompt: 0.0000025, completion: 0.00001, inputCacheRead: 0.00000125 },
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "openai/gpt-4o-mini",
|
|
66
|
+
category: "chat",
|
|
67
|
+
pricing: { prompt: 1.5e-7, completion: 6e-7, inputCacheRead: 7.5e-8 },
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "openai/o3",
|
|
71
|
+
category: "reasoning",
|
|
72
|
+
pricing: { prompt: 0.000002, completion: 0.000008, inputCacheRead: 5e-7, webSearch: 0.01 },
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "openai/o3-mini",
|
|
76
|
+
category: "reasoning",
|
|
77
|
+
pricing: { prompt: 0.0000011, completion: 0.0000044, inputCacheRead: 5.5e-7 },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "openai/o4-mini",
|
|
81
|
+
category: "reasoning",
|
|
82
|
+
pricing: { prompt: 0.0000011, completion: 0.0000044, inputCacheRead: 2.75e-7, webSearch: 0.01 },
|
|
83
|
+
},
|
|
84
|
+
] as const;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Context, ExecutionContext } from "@/lib/context.js";
|
|
2
|
+
import { createMockLogger } from "@/testing/logger.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a mock {@link ExecutionContext} with a mock logger
|
|
6
|
+
* and a live (non-aborted) signal.
|
|
7
|
+
*
|
|
8
|
+
* Pass `overrides` to replace individual fields.
|
|
9
|
+
*/
|
|
10
|
+
export function createMockExecutionCtx(overrides?: Partial<ExecutionContext>): ExecutionContext {
|
|
11
|
+
return {
|
|
12
|
+
signal: new AbortController().signal,
|
|
13
|
+
log: createMockLogger(),
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a mock {@link Context} with an empty trace, a mock logger,
|
|
20
|
+
* and a live (non-aborted) signal.
|
|
21
|
+
*
|
|
22
|
+
* Pass `overrides` to replace individual fields.
|
|
23
|
+
*/
|
|
24
|
+
export function createMockCtx(overrides?: Partial<Context>): Context {
|
|
25
|
+
return {
|
|
26
|
+
signal: new AbortController().signal,
|
|
27
|
+
log: createMockLogger(),
|
|
28
|
+
trace: [],
|
|
29
|
+
messages: [],
|
|
30
|
+
...overrides,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type { Logger } from "@/core/logger.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a mock {@link Logger} backed by `vi.fn()` stubs.
|
|
7
|
+
*
|
|
8
|
+
* Every method is a no-op spy. `child()` returns a fresh mock
|
|
9
|
+
* logger so nested scopes work without blowing up.
|
|
10
|
+
*/
|
|
11
|
+
export function createMockLogger(): Logger {
|
|
12
|
+
return {
|
|
13
|
+
debug: vi.fn(),
|
|
14
|
+
info: vi.fn(),
|
|
15
|
+
warn: vi.fn(),
|
|
16
|
+
error: vi.fn(),
|
|
17
|
+
child: vi.fn(() => createMockLogger()),
|
|
18
|
+
} as unknown as Logger;
|
|
19
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { attemptEach, attemptEachAsync } from "@/utils/attempt.js";
|
|
4
|
+
|
|
5
|
+
describe("attemptEach", () => {
|
|
6
|
+
it("returns success tuples for handlers that succeed", () => {
|
|
7
|
+
const results = attemptEach(
|
|
8
|
+
() => 1,
|
|
9
|
+
() => 2,
|
|
10
|
+
);
|
|
11
|
+
expect(results).toEqual([
|
|
12
|
+
[null, 1],
|
|
13
|
+
[null, 2],
|
|
14
|
+
]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns error tuples for handlers that throw", () => {
|
|
18
|
+
const results = attemptEach(
|
|
19
|
+
() => {
|
|
20
|
+
throw new Error("boom");
|
|
21
|
+
},
|
|
22
|
+
() => 42,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(results).toHaveLength(2);
|
|
26
|
+
const first = results[0];
|
|
27
|
+
if (first == null) {
|
|
28
|
+
throw new Error("Expected first result to be defined");
|
|
29
|
+
}
|
|
30
|
+
expect(first[0]).toBeInstanceOf(Error);
|
|
31
|
+
expect((first[0] as Error).message).toBe("boom");
|
|
32
|
+
expect(first[1]).toBeNull();
|
|
33
|
+
expect(results[1]).toEqual([null, 42]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("skips undefined handlers", () => {
|
|
37
|
+
const results = attemptEach(
|
|
38
|
+
undefined,
|
|
39
|
+
() => "a",
|
|
40
|
+
undefined,
|
|
41
|
+
() => "b",
|
|
42
|
+
undefined,
|
|
43
|
+
);
|
|
44
|
+
expect(results).toEqual([
|
|
45
|
+
[null, "a"],
|
|
46
|
+
[null, "b"],
|
|
47
|
+
]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns an empty array when all handlers are undefined", () => {
|
|
51
|
+
const results = attemptEach(undefined, undefined);
|
|
52
|
+
expect(results).toEqual([]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns an empty array when called with no arguments", () => {
|
|
56
|
+
const results = attemptEach();
|
|
57
|
+
expect(results).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("attemptEachAsync", () => {
|
|
62
|
+
it("returns success tuples for async handlers that succeed", async () => {
|
|
63
|
+
const results = await attemptEachAsync(
|
|
64
|
+
async () => "a",
|
|
65
|
+
async () => "b",
|
|
66
|
+
);
|
|
67
|
+
expect(results).toEqual([
|
|
68
|
+
[null, "a"],
|
|
69
|
+
[null, "b"],
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("returns error tuples for async handlers that reject", async () => {
|
|
74
|
+
const results = await attemptEachAsync(
|
|
75
|
+
async () => {
|
|
76
|
+
throw new Error("async boom");
|
|
77
|
+
},
|
|
78
|
+
async () => "ok",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(results).toHaveLength(2);
|
|
82
|
+
const first = results[0];
|
|
83
|
+
if (first == null) {
|
|
84
|
+
throw new Error("Expected first result to be defined");
|
|
85
|
+
}
|
|
86
|
+
expect(first[0]).toBeInstanceOf(Error);
|
|
87
|
+
expect((first[0] as Error).message).toBe("async boom");
|
|
88
|
+
expect(first[1]).toBeNull();
|
|
89
|
+
expect(results[1]).toEqual([null, "ok"]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("handles sync handlers passed to the async variant", async () => {
|
|
93
|
+
const results = await attemptEachAsync(() => "sync");
|
|
94
|
+
expect(results).toEqual([[null, "sync"]]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("skips undefined handlers", async () => {
|
|
98
|
+
const results = await attemptEachAsync(undefined, async () => 1, undefined);
|
|
99
|
+
expect(results).toEqual([[null, 1]]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("returns an empty array when all handlers are undefined", async () => {
|
|
103
|
+
const results = await attemptEachAsync(undefined);
|
|
104
|
+
expect(results).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("returns an empty array when called with no arguments", async () => {
|
|
108
|
+
const results = await attemptEachAsync();
|
|
109
|
+
expect(results).toEqual([]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("executes handlers sequentially", async () => {
|
|
113
|
+
const order: number[] = [];
|
|
114
|
+
await attemptEachAsync(
|
|
115
|
+
async () => {
|
|
116
|
+
order.push(1);
|
|
117
|
+
},
|
|
118
|
+
async () => {
|
|
119
|
+
order.push(2);
|
|
120
|
+
},
|
|
121
|
+
async () => {
|
|
122
|
+
order.push(3);
|
|
123
|
+
},
|
|
124
|
+
);
|
|
125
|
+
expect(order).toEqual([1, 2, 3]);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { attempt, attemptAsync } from "es-toolkit";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run N callbacks sequentially, swallowing errors.
|
|
5
|
+
*
|
|
6
|
+
* Returns an array of `[error, result]` tuples following the
|
|
7
|
+
* same convention as `attempt` from es-toolkit.
|
|
8
|
+
*
|
|
9
|
+
* @param handlers - Callbacks to execute in order. `undefined` entries are skipped.
|
|
10
|
+
* @returns An array of attempt results for each non-undefined handler.
|
|
11
|
+
*/
|
|
12
|
+
export function attemptEach<T = void, E = Error>(
|
|
13
|
+
...handlers: Array<(() => T) | undefined>
|
|
14
|
+
): Array<[null, T] | [E, null]> {
|
|
15
|
+
return handlers.filter((h): h is () => T => h != null).map((h) => attempt<T, E>(h));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Run N async callbacks sequentially, swallowing errors.
|
|
20
|
+
*
|
|
21
|
+
* Returns an array of `[error, result]` tuples following the
|
|
22
|
+
* same convention as `attemptAsync` from es-toolkit.
|
|
23
|
+
*
|
|
24
|
+
* @param handlers - Callbacks to execute in order. `undefined` entries are skipped.
|
|
25
|
+
* @returns An array of attempt results for each non-undefined handler.
|
|
26
|
+
*/
|
|
27
|
+
export async function attemptEachAsync<T = void, E = Error>(
|
|
28
|
+
...handlers: Array<(() => T | Promise<T>) | undefined>
|
|
29
|
+
): Promise<Array<[null, T] | [E, null]>> {
|
|
30
|
+
const results: Array<[null, T] | [E, null]> = [];
|
|
31
|
+
for (const h of handlers) {
|
|
32
|
+
if (h != null) {
|
|
33
|
+
// oxlint-disable-next-line no-await-in-loop - sequential by design
|
|
34
|
+
results.push(await attemptAsync<T, E>(async () => h()));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|