@open-autonomy/sdk 2.3.1 → 2.3.3

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 CHANGED
@@ -29,8 +29,8 @@ await s.end({ outcome: 'done', report: 'Done. add — committed 7d30729.', commi
29
29
  page parses through the same code. There is no write API: the file in git is the only roadmap surface.
30
30
  Adapters that mirror it to a tracker are what the shape is for.
31
31
 
32
- Spend is attributed by the platform: a metered call settles on the one session live at that moment, so
33
- an item's page shows every session, update and settled cent that touched it.
32
+ Spend is attributed by the platform: Hermes names its session on each model request, so overlapping sessions
33
+ each receive their own settled calls and cents and an item's page shows everything that touched it.
34
34
 
35
35
  ## Drivers
36
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-autonomy/sdk",
3
- "version": "2.3.1",
3
+ "version": "2.3.3",
4
4
  "description": "The Open Autonomy SDK: the roadmap model and its codec, the development-stream client (sessions, turns, updates), and the key helpers. Everything it does is a documented HTTP wire any language can speak without it.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/client.ts CHANGED
@@ -19,7 +19,7 @@ export type TurnRole = 'user' | 'assistant' | 'tool' | 'system';
19
19
  export interface Turn { ts?: string; role: TurnRole; text?: string; tool?: string; args?: string; result?: string }
20
20
  export type SessionOutcome = 'done' | 'failed';
21
21
 
22
- export interface SessionStart { key: string; kind?: string; title?: string; item?: string; source?: string; startedAt?: string }
22
+ export interface SessionStart { key: string; kind?: string; title?: string; item?: string; source?: string; modelProvider?: string; startedAt?: string }
23
23
  export interface SessionEnd { key: string; outcome?: SessionOutcome; report?: string; commit?: string; item?: string; endedAt?: string }
24
24
  export interface Update { item: string; text: string; session?: string; at?: string }
25
25
  // The board's state for a roadmap item, as the agent's harness keeps it: the task's lane, every attempt at
@@ -57,7 +57,7 @@ export const EVENT_TYPES = {
57
57
  } as const;
58
58
 
59
59
  export function sessionStartedEvent(s: SessionStart, source = 'open-autonomy-sdk'): CloudEvent {
60
- return event(EVENT_TYPES.started, s.key, { session_kind: s.kind, title: s.title, item_id: s.item, source: s.source }, s.startedAt, source);
60
+ return event(EVENT_TYPES.started, s.key, { session_kind: s.kind, title: s.title, item_id: s.item, source: s.source, model_provider: s.modelProvider }, s.startedAt, source);
61
61
  }
62
62
  export function sessionTurnsEvent(key: string, seq: number, turns: Turn[], item?: string, source = 'open-autonomy-sdk'): CloudEvent {
63
63
  return event(EVENT_TYPES.turns, key, { seq, turns, item_id: item }, undefined, source);
@@ -76,7 +76,7 @@ function event(type: string, subject: string, data: Record<string, unknown>, tim
76
76
  export interface EventResult { id?: string; ok: boolean; error?: string; idempotent?: boolean; session?: SessionSummary; update?: UpdateRecord }
77
77
  export interface SessionSummary {
78
78
  key: string; account: string; kind: string; status: 'live' | 'ended'; outcome?: SessionOutcome; title?: string; item_id?: string; source?: string;
79
- started_at: string; ended_at?: string; report?: string; commit_sha?: string; turn_count: number; next_seq: number; tool_calls: number; usd_cents: number; calls: number; updated_at: string;
79
+ model_provider?: string; started_at: string; ended_at?: string; report?: string; commit_sha?: string; turn_count: number; next_seq: number; tool_calls: number; usd_cents: number; calls: number; updated_at: string;
80
80
  }
81
81
  export interface SessionRecord extends Omit<SessionSummary, 'tool_calls'> { turns: Array<Turn & { seq?: number }> }
82
82
  export interface UpdateRecord { id: string; account: string; item_id: string; ts: string; text: string; session?: string }
package/src/rails.ts CHANGED
@@ -72,21 +72,56 @@ export function parseModelsBound(yaml: string): string[] {
72
72
  return out;
73
73
  }
74
74
 
75
- // The most the project's funds may spend on the model rail in a day (UTC), from the same file:
75
+ // The limits the project's funds run under on the model rail, from the same file the shape a provider's rate
76
+ // limits and a budget's period share: each limit is one measure over one window, rolling.
76
77
  // spend:
77
- // daily_usd_cents: 500
78
- // 0 or absent: no bound of the project's own beyond the platform's global rail.
79
- export function parseSpendBound(yaml: string): { daily_usd_cents: number } {
80
- let block = '';
81
- let daily = 0;
78
+ // limits:
79
+ // - { window: 1d, usd_cents: 500 } # the money: cents over the window
80
+ // - { window: 30d, usd_cents: 6000 }
81
+ // - { window: 1m, calls: 60 } # the pace: requests over the window
82
+ // - { window: 1h, tokens: 2000000 } # the volume: tokens in and out over the window
83
+ // - { window: 1d, usd_cents: 100, model: openai/gpt-5 } # one model's own budget
84
+ // Windows: N m (minutes, up to 60), N h (hours, up to 24), N d (days, up to 31). Every limit holds at once; the platform
85
+ // refuses the call that would cross one and says which. None: no bound of the project's own beyond the platform's rail.
86
+ export interface SpendLimit { window: string; window_seconds: number; usd_cents?: number; calls?: number; tokens?: number; model?: string }
87
+ export function parseWindow(spec: string): number | undefined {
88
+ const m = /^(\d+)\s*(m|h|d)$/.exec(spec.trim());
89
+ if (!m) return undefined;
90
+ const n = Number(m[1]); const unit = { m: 60, h: 3600, d: 86400 }[m[2] as 'm' | 'h' | 'd'];
91
+ const max = { m: 60, h: 24, d: 31 }[m[2] as 'm' | 'h' | 'd'];
92
+ return n >= 1 && n <= max ? n * unit : undefined;
93
+ }
94
+ export function parseSpendLimits(yaml: string): SpendLimit[] {
95
+ const out: SpendLimit[] = [];
96
+ let block = ''; let inLimits = false; let item: Record<string, string> | null = null;
97
+ const flush = () => {
98
+ if (!item) return;
99
+ const seconds = item.window ? parseWindow(item.window) : undefined;
100
+ const num = (k: string) => (item![k] !== undefined && /^\d+$/.test(item![k]) ? Number(item![k]) : undefined);
101
+ const limit: SpendLimit = { window: (item.window ?? '').trim(), window_seconds: seconds ?? 0 };
102
+ if (num('usd_cents') !== undefined) limit.usd_cents = num('usd_cents');
103
+ if (num('calls') !== undefined) limit.calls = num('calls');
104
+ if (num('tokens') !== undefined) limit.tokens = num('tokens');
105
+ if (item.model) limit.model = item.model.trim().replace(/^["']|["']$/g, '');
106
+ if (seconds && (limit.usd_cents !== undefined || limit.calls !== undefined || limit.tokens !== undefined)) out.push(limit);
107
+ item = null;
108
+ };
109
+ const pairs = (text: string): Record<string, string> => Object.fromEntries(text.split(',').map((kv) => kv.split(':')).filter((kv) => kv.length >= 2).map(([k, ...v]) => [k.trim(), v.join(':').trim().replace(/^["']|["']$/g, '')]));
82
110
  for (const raw of yaml.split('\n')) {
83
111
  const line = raw.replace(/\s+#.*$/, '').trimEnd();
84
112
  if (!line.trim() || line.trim().startsWith('#')) continue;
85
113
  const top = /^([a-z_]+):\s*(.*)$/.exec(line);
86
- if (top) { block = top[2] === '' ? top[1] : ''; continue; }
114
+ if (top) { flush(); block = top[2] === '' ? top[1] : ''; inLimits = false; continue; }
87
115
  if (block !== 'spend') continue;
88
- const kv = /^\s+daily_usd_cents:\s*(\d+)\s*$/.exec(line);
89
- if (kv) daily = Math.max(0, Math.floor(Number(kv[1])));
116
+ if (/^ limits:\s*$/.test(line)) { flush(); inLimits = true; continue; }
117
+ if (!inLimits) continue;
118
+ const flow = /^\s+-\s*\{(.*)\}\s*$/.exec(line);
119
+ if (flow) { flush(); item = pairs(flow[1]); flush(); continue; }
120
+ const start = /^\s+-\s+([a-z_]+):\s*(.+)$/.exec(line);
121
+ if (start) { flush(); item = { [start[1]]: start[2].trim() }; continue; }
122
+ const more = item ? /^\s+([a-z_]+):\s*(.+)$/.exec(line) : null;
123
+ if (more) item![more[1]] = more[2].trim();
90
124
  }
91
- return { daily_usd_cents: daily };
125
+ flush();
126
+ return out;
92
127
  }