@inixiative/foundry-core 0.5.9 → 0.5.10

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 (50) hide show
  1. package/package.json +4 -4
  2. package/src/action-prompt.ts +11 -2
  3. package/src/adapters/file-memory.ts +5 -0
  4. package/src/adapters/http-memory.ts +5 -0
  5. package/src/adapters/markdown-docs.ts +5 -0
  6. package/src/adapters/markdown-hierarchy.ts +7 -2
  7. package/src/adapters/sqlite-memory.ts +21 -6
  8. package/src/base-agent.ts +5 -0
  9. package/src/bounded-set.ts +5 -0
  10. package/src/cache-lifecycle.ts +5 -0
  11. package/src/capability.ts +5 -0
  12. package/src/clarifier.ts +5 -0
  13. package/src/classifier.ts +5 -0
  14. package/src/context-layer.ts +10 -0
  15. package/src/context-stack.ts +5 -0
  16. package/src/credentials.ts +5 -0
  17. package/src/decider.ts +5 -0
  18. package/src/delivery-evidence.ts +5 -0
  19. package/src/event-stream.ts +5 -0
  20. package/src/executor.ts +17 -4
  21. package/src/feedback/archive-turns.ts +5 -0
  22. package/src/feedback/correction.ts +13 -1
  23. package/src/feedback/experiment-gate.ts +5 -0
  24. package/src/feedback/review-findings.ts +5 -0
  25. package/src/feedback/warden-labels.ts +5 -0
  26. package/src/feedback/warden-prefilter.ts +5 -0
  27. package/src/feedback/warden-probes.ts +5 -0
  28. package/src/harness.ts +7 -2
  29. package/src/hydrator.ts +5 -0
  30. package/src/id.ts +5 -0
  31. package/src/index.ts +23 -11
  32. package/src/intervention.ts +5 -0
  33. package/src/messages.ts +5 -0
  34. package/src/middleware.ts +5 -0
  35. package/src/model-registry.ts +92 -2
  36. package/src/native-evidence.ts +18 -0
  37. package/src/native-tools.ts +5 -0
  38. package/src/retry.ts +5 -0
  39. package/src/router.ts +5 -0
  40. package/src/scope.ts +5 -0
  41. package/src/serialized-queue.ts +35 -0
  42. package/src/session-delivery.ts +5 -0
  43. package/src/signal.ts +5 -0
  44. package/src/thread.ts +196 -6
  45. package/src/token-counts.ts +5 -0
  46. package/src/token-tracker.ts +49 -24
  47. package/src/tools.ts +6 -1
  48. package/src/trace.ts +5 -0
  49. package/src/types.ts +5 -0
  50. package/src/hooks.ts +0 -133
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf feature:usage
5
+ */
1
6
  import { sumTokenCounts, type TokenCounts, totalTokenCount } from './token-counts';
2
7
 
3
8
  // ---------------------------------------------------------------------------
@@ -140,6 +145,10 @@ const attributionMatches = (
140
145
  export interface ModelPricing {
141
146
  readonly inputPer1M: number;
142
147
  readonly outputPer1M: number;
148
+ /** Cache reads; default a tenth of input. */
149
+ readonly cacheReadPer1M?: number;
150
+ /** Cache writes (5-minute); default 1.25x input. */
151
+ readonly cacheWritePer1M?: number;
143
152
  }
144
153
 
145
154
  /**
@@ -209,11 +218,22 @@ export interface UsageSummary {
209
218
  }
210
219
 
211
220
  // ---------------------------------------------------------------------------
212
- // Default cost table (approximate pricing as of early 2026)
221
+ // Default cost table. Anthropic: first-party API list prices per 1M tokens, as published at
222
+ // https://platform.claude.com/docs/en/about-claude/pricing (read 2026-11-01; cache writes are the
223
+ // 5-minute rate, 1.25x input). Haiku 5.5 is its rate up to 100K-token prompts.
213
224
  // ---------------------------------------------------------------------------
214
225
 
215
226
  export const DEFAULT_COST_TABLE: CostTable = {
216
227
  anthropic: {
228
+ 'claude-fable-5-1': { inputPer1M: 10, outputPer1M: 50, cacheReadPer1M: 0.25 },
229
+ 'claude-fable-5': { inputPer1M: 10, outputPer1M: 50 },
230
+ 'claude-opus-5-5': { inputPer1M: 4, outputPer1M: 20, cacheReadPer1M: 0.2 },
231
+ 'claude-opus-5': { inputPer1M: 5, outputPer1M: 25 },
232
+ 'claude-opus-4-8': { inputPer1M: 5, outputPer1M: 25 },
233
+ 'claude-sonnet-5-5': { inputPer1M: 2, outputPer1M: 10, cacheReadPer1M: 0.2 },
234
+ 'claude-sonnet-5': { inputPer1M: 2, outputPer1M: 10 },
235
+ 'claude-haiku-5-5': { inputPer1M: 0.1, outputPer1M: 0.5 },
236
+ 'claude-haiku-4-5': { inputPer1M: 1, outputPer1M: 5 },
217
237
  'claude-opus-4-7': { inputPer1M: 5, outputPer1M: 25 },
218
238
  'claude-opus-4-6': { inputPer1M: 5, outputPer1M: 25 },
219
239
  'claude-sonnet-4-6': { inputPer1M: 3, outputPer1M: 15 },
@@ -239,10 +259,24 @@ export const DEFAULT_COST_TABLE: CostTable = {
239
259
  'muse-spark-1.2-contributor': { inputPer1M: 0.1, outputPer1M: 0.2 },
240
260
  'muse-spark-1.1': { inputPer1M: 1.25, outputPer1M: 4.25 },
241
261
  },
242
- // Claude Code provider tracks usage via session output, not cost table
243
- 'claude-code': {},
244
262
  };
245
263
 
264
+ /**
265
+ * A model's price: its own provider's row, else the same model id under any provider. A Claude
266
+ * model run through the Claude Code login (provider `claude-code` or a decision host) is priced at
267
+ * its API list price: an API-equivalent cost, not what a subscription bills.
268
+ */
269
+ export function modelPricing(
270
+ table: CostTable,
271
+ provider: string,
272
+ model: string,
273
+ ): ModelPricing | undefined {
274
+ const own = table[provider]?.[model];
275
+ if (own) return own;
276
+ for (const rows of Object.values(table)) if (rows[model]) return rows[model];
277
+ return undefined;
278
+ }
279
+
246
280
  // ---------------------------------------------------------------------------
247
281
  // Token estimation
248
282
  // ---------------------------------------------------------------------------
@@ -316,19 +350,14 @@ export class TokenTracker {
316
350
  * Throws if budget.haltAt is exceeded (when configured).
317
351
  */
318
352
  record(entry: Omit<UsageEntry, 'timestamp' | 'cost'>): UsageEntry {
319
- const cost = this._computeCost(
320
- entry.provider,
321
- entry.model,
322
- entry.tokens.input,
323
- entry.tokens.output,
324
- entry.cached,
325
- );
353
+ const pricing = modelPricing(this._costTable, entry.provider, entry.model);
354
+ const cost = pricing ? this._computeCost(pricing, entry.tokens, entry.cached) : 0;
326
355
 
327
356
  const full: UsageEntry = {
328
357
  ...entry,
329
358
  timestamp: Date.now(),
330
359
  cost,
331
- costKnown: !!this._costTable[entry.provider]?.[entry.model],
360
+ costKnown: !!pricing,
332
361
  };
333
362
 
334
363
  this._entries.push(full);
@@ -474,20 +503,16 @@ export class TokenTracker {
474
503
  // Private helpers
475
504
  // -----------------------------------------------------------------------
476
505
 
477
- private _computeCost(
478
- provider: string,
479
- model: string,
480
- input: number,
481
- output: number,
482
- cached?: boolean,
483
- ): number {
484
- const pricing = this._costTable[provider]?.[model];
485
- if (!pricing) return 0;
486
-
487
- // Cached input tokens are typically free or heavily discounted
488
- const effectiveInput = cached ? input * 0.1 : input;
506
+ private _computeCost(pricing: ModelPricing, tokens: TokenCounts, cached?: boolean): number {
507
+ // `cached` marks a whole call served from a provider cache that reports no split.
508
+ const input = cached ? tokens.input * 0.1 : tokens.input;
509
+ const perToken = (count: number | undefined, per1M: number) =>
510
+ ((count ?? 0) / 1_000_000) * per1M;
489
511
  return (
490
- (effectiveInput / 1_000_000) * pricing.inputPer1M + (output / 1_000_000) * pricing.outputPer1M
512
+ perToken(input, pricing.inputPer1M) +
513
+ perToken(tokens.output, pricing.outputPer1M) +
514
+ perToken(tokens.cacheRead, pricing.cacheReadPer1M ?? pricing.inputPer1M * 0.1) +
515
+ perToken(tokens.cacheWrite, pricing.cacheWritePer1M ?? pricing.inputPer1M * 1.25)
491
516
  );
492
517
  }
493
518
 
package/src/tools.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind type
4
+ * @partOf primitive:tools
5
+ */
1
6
  // ---------------------------------------------------------------------------
2
7
  // Tools — typed interfaces for agent execution environments
3
8
  // ---------------------------------------------------------------------------
@@ -721,7 +726,7 @@ export class ToolRegistry {
721
726
  const api = tool.scoped ? tool.scoped(opts?.scope ?? {}) : tool;
722
727
  return await api.request({
723
728
  url: input.url as string,
724
- method: (input.method as any) ?? 'GET',
729
+ method: (input.method as ApiRequest['method']) ?? 'GET',
725
730
  body: input.body,
726
731
  headers: input.headers as Record<string, string>,
727
732
  });
package/src/trace.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind service
4
+ * @partOf primitive:traces
5
+ */
1
6
  import type { TokenCounts } from './token-counts';
2
7
  /**
3
8
  * A single span in a message's journey through the system.
package/src/types.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @atlas
3
+ * @kind type
4
+ * @partOf primitive:agents
5
+ */
1
6
  import type { TokenCounts } from './token-counts';
2
7
  // ---------------------------------------------------------------------------
3
8
  // Shared types — used by harness for conditional invocation/activation
package/src/hooks.ts DELETED
@@ -1,133 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Types
3
- // ---------------------------------------------------------------------------
4
-
5
- export type HookPoint =
6
- | 'pre:dispatch'
7
- | 'post:dispatch'
8
- | 'pre:classify'
9
- | 'post:classify'
10
- | 'pre:route'
11
- | 'post:route'
12
- | 'pre:compact'
13
- | 'post:compact'
14
- | 'session:create'
15
- | 'session:archive'
16
- | 'budget:warning'
17
- | 'budget:exceeded'
18
- | 'plan:enter'
19
- | 'plan:exit'
20
- | 'error:agent'
21
- | 'error:provider';
22
-
23
- export interface HookContext {
24
- hookPoint: HookPoint;
25
- threadId?: string;
26
- agentId?: string;
27
- payload?: unknown;
28
- result?: unknown;
29
- meta: Record<string, unknown>;
30
- timestamp: number;
31
- }
32
-
33
- export interface HookResult {
34
- action: 'continue' | 'skip' | 'abort' | 'redirect';
35
- redirectTo?: string;
36
- modified?: Partial<HookContext>;
37
- annotations?: Record<string, unknown>;
38
- }
39
-
40
- export interface HookHandler {
41
- id: string;
42
- points: HookPoint[];
43
- priority?: number;
44
- handler: (ctx: HookContext) => Promise<HookResult>;
45
- }
46
-
47
- export interface PlanModeTrigger {
48
- kind: 'complexity' | 'newDomain' | 'largeDiff' | 'custom';
49
- threshold?: number;
50
- detect?: (ctx: HookContext) => boolean;
51
- }
52
-
53
- export interface PlanModeConfig {
54
- triggers: PlanModeTrigger[];
55
- plannerAgentId: string;
56
- requireApproval?: boolean;
57
- }
58
-
59
- // ---------------------------------------------------------------------------
60
- // HookRegistry — engine mechanism, no built-in behaviors
61
- // ---------------------------------------------------------------------------
62
-
63
- const DEFAULT_PRIORITY = 100;
64
-
65
- export class HookRegistry {
66
- private _handlers: Map<string, HookHandler> = new Map();
67
-
68
- register(handler: HookHandler): () => void {
69
- this._handlers.set(handler.id, handler);
70
- return () => {
71
- this._handlers.delete(handler.id);
72
- };
73
- }
74
-
75
- unregister(id: string): boolean {
76
- return this._handlers.delete(id);
77
- }
78
-
79
- async execute(
80
- point: HookPoint,
81
- ctx: HookContext,
82
- ): Promise<HookContext & { action: HookResult['action'] }> {
83
- const handlers = this.forPoint(point);
84
- let current = { ...ctx };
85
- let action: HookResult['action'] = 'continue';
86
-
87
- for (const handler of handlers) {
88
- const result = await handler.handler(current);
89
- action = result.action;
90
-
91
- if (result.modified) {
92
- current = {
93
- ...current,
94
- ...result.modified,
95
- meta: { ...current.meta, ...result.modified.meta },
96
- };
97
- }
98
-
99
- if (result.annotations) {
100
- current.meta = { ...current.meta, ...result.annotations };
101
- }
102
-
103
- if (result.redirectTo) {
104
- current.meta = { ...current.meta, redirectTo: result.redirectTo };
105
- }
106
-
107
- if (action === 'abort' || action === 'skip' || action === 'redirect') {
108
- break;
109
- }
110
- }
111
-
112
- return { ...current, action };
113
- }
114
-
115
- forPoint(point: HookPoint): ReadonlyArray<HookHandler> {
116
- const matching: HookHandler[] = [];
117
- for (const handler of this._handlers.values()) {
118
- if (handler.points.includes(point)) {
119
- matching.push(handler);
120
- }
121
- }
122
- matching.sort((a, b) => (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY));
123
- return matching;
124
- }
125
-
126
- get size(): number {
127
- return this._handlers.size;
128
- }
129
-
130
- get handlers(): ReadonlyArray<HookHandler> {
131
- return [...this._handlers.values()];
132
- }
133
- }