@wice24/tars-sdk 0.2.4 → 0.3.1

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/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import type { Agent, AgentVersion, ClientTool, CreateAgentInput, FileMeta, ListMcpToolsInput, McpCredentialInput, McpCredentialStatus, McpServerTools, ReasoningConfig, SessionEvent, ModelInfo, SessionInfo, SessionListItem, UpdateAgentInput, UploadFileInput, UsageSummary, SessionUsageRow, DailyUsageRow, McpServer, Skill, SkillVersion, ApiKeyInfo, ClientIdentity, ClientRole, PlatformStats, Tenant } from './types.js';
1
+ import type { Agent, AgentVersion, ClientTool, CompactResult, CreateAgentInput, FileMeta, ListMcpToolsInput, McpCredentialInput, McpCredentialStatus, McpServerTools, ReasoningConfig, SessionEvent, ModelInfo, ProviderKeys, ProviderStatusReport, SessionInfo, SessionListItem, UpdateAgentInput, UploadFileInput, UsageSummary, SessionUsageRow, DailyUsageRow, McpServer, Skill, SkillVersion, ApiKeyInfo, ClientIdentity, ClientRole, PlatformStats, Tenant } from './types.js';
2
2
  type ToolHandler = (input: any) => unknown | Promise<unknown>;
3
3
  export * from './types.js';
4
+ /** The providers `providerKeys` accepts. */
5
+ export declare const KEYED_PROVIDERS: readonly ["anthropic", "openai", "openrouter", "tensorx"];
4
6
  export declare class TarsApiError extends Error {
5
7
  readonly status: number;
6
8
  readonly body: unknown;
@@ -15,20 +17,35 @@ export interface TarsClientOptions {
15
17
  * Admin keys only: scope every request to this tenant id (sent as the
16
18
  * `X-Tars-Tenant` header). Act-as is READ-ONLY — GETs return the target
17
19
  * tenant's data; any write is refused with 403 `act_as_read_only`, and so
18
- * is live SSE (`sessions.stream` / `resumeStream` — sandbox reattachment is
19
- * a side effect): read the journal via `sessions.get` / `sessions.events`
20
- * instead. Non-admin keys get 403 `admin_required`; an unknown tenant id
20
+ * is live SSE (`streamEvents` / `TarsSession.stream` / `resume` — sandbox
21
+ * reattachment is a side effect): read the journal via `sessions.get` /
22
+ * `sessions.events` instead. Non-admin keys get 403 `admin_required`; an unknown tenant id
21
23
  * 404s on every call; an empty value 400s. The effective role is always
22
24
  * `tenant`, so `admin.*` calls under act-as also get 403 `admin_required`
23
25
  * — use a client WITHOUT this option to administrate.
24
26
  */
25
27
  actAsTenant?: string;
28
+ /**
29
+ * Your own LLM provider keys, by provider (`openrouter`, `openai`,
30
+ * `anthropic`, `tensorx`). Sent with EVERY request (one
31
+ * `x-tars-key-<provider>` header each) and used for that request's model
32
+ * calls — session turns, completions, the model catalog — so usage bills to
33
+ * your provider accounts. TARS never stores them. A provider you omit
34
+ * routes on the instance's key only while that instance's cutover fallback
35
+ * is on; otherwise its models are refused with 400
36
+ * `provider_not_configured`. Check with `providerStatus()`.
37
+ *
38
+ * SERVER-SIDE clients only: in a browser these keys would ship to every
39
+ * user of your app.
40
+ */
41
+ providerKeys?: ProviderKeys;
26
42
  }
27
43
  export declare class TarsClient {
28
44
  private readonly baseUrl;
29
45
  private readonly apiKey;
30
46
  private readonly fetchImpl;
31
47
  private readonly actAsTenant?;
48
+ private readonly providerKeyHeaders;
32
49
  constructor(options: TarsClientOptions);
33
50
  /** Every request path — JSON, uploads, SSE — sends these, so act-as scoping
34
51
  * can never silently miss a surface. */
@@ -41,11 +58,15 @@ export declare class TarsClient {
41
58
  }) => Promise<Agent[]>;
42
59
  get: (id: string) => Promise<Agent>;
43
60
  update: (id: string, patch: UpdateAgentInput) => Promise<Agent>;
61
+ /** Archived agents refuse new sessions (409) and updates; existing
62
+ * sessions keep running. */
44
63
  archive: (id: string) => Promise<{
45
64
  ok: boolean;
65
+ status: "archived";
46
66
  }>;
47
67
  unarchive: (id: string) => Promise<{
48
68
  ok: boolean;
69
+ status: "active";
49
70
  }>;
50
71
  versions: (id: string) => Promise<AgentVersion[]>;
51
72
  };
@@ -79,6 +100,20 @@ export declare class TarsClient {
79
100
  rename: (id: string, title: string) => Promise<{
80
101
  ok: boolean;
81
102
  }>;
103
+ /**
104
+ * One page of the session's journal — the same events `get()` inlines,
105
+ * for incremental readers on long sessions. `afterSeq` is the last seq
106
+ * you hold (-1 = from the start); `limit` 1..1000 (default 500).
107
+ * `has_more` says another page follows. Allowed under act-as, unlike the
108
+ * live stream.
109
+ */
110
+ events: (id: string, opts?: {
111
+ afterSeq?: number;
112
+ limit?: number;
113
+ }) => Promise<{
114
+ events: SessionEvent[];
115
+ has_more: boolean;
116
+ }>;
82
117
  /** Replace the session's extra MCP server set (takes effect next turn;
83
118
  * 409s while a turn is in flight). */
84
119
  setMcpServers: (id: string, servers: McpServer[]) => Promise<{
@@ -95,6 +130,17 @@ export declare class TarsClient {
95
130
  ok: boolean;
96
131
  interrupted: boolean;
97
132
  }>;
133
+ /**
134
+ * Compact the session's context now: the older history is folded into a
135
+ * checkpoint summary and the recent tail kept verbatim — what the
136
+ * platform does on its own as a session nears its model's context
137
+ * window (the `compaction` event either way). Idle sessions only
138
+ * (`turn_in_progress` otherwise). `instructions` is an optional focus
139
+ * for the summary ("keep every SQL statement verbatim").
140
+ */
141
+ compact: (id: string, opts?: {
142
+ instructions?: string;
143
+ }) => Promise<CompactResult>;
98
144
  usage: (id: string) => Promise<{
99
145
  session_id: string;
100
146
  model: string;
@@ -146,12 +192,17 @@ export declare class TarsClient {
146
192
  list: () => Promise<Skill[]>;
147
193
  get: (id: string) => Promise<Skill>;
148
194
  versions: (id: string) => Promise<SkillVersion[]>;
149
- /** Upload a new immutable version from zip bytes. */
195
+ /** Upload a new immutable version from zip bytes. `name` / `description`
196
+ * are what SKILL.md's front matter declared (null when absent); `files`
197
+ * counts the bundle's entries. */
150
198
  pushVersion: (id: string, zipBytes: Uint8Array) => Promise<{
151
199
  skill_id: string;
152
200
  slug: string;
153
201
  version: number;
202
+ name: string | null;
203
+ description: string | null;
154
204
  files: number;
205
+ size_bytes: number;
155
206
  }>;
156
207
  /**
157
208
  * Download one immutable version's zip bytes — exactly what `pushVersion`
@@ -160,12 +211,14 @@ export declare class TarsClient {
160
211
  downloadVersion: (id: string, version: number) => Promise<Uint8Array>;
161
212
  };
162
213
  readonly apiKeys: {
214
+ /** Mint another key for this tenant. `key` is returned ONCE. */
163
215
  create: (input?: {
164
216
  name?: string;
165
217
  }) => Promise<{
166
218
  id: string;
167
219
  key: string;
168
220
  key_prefix: string;
221
+ name: string | null;
169
222
  }>;
170
223
  list: () => Promise<ApiKeyInfo[]>;
171
224
  revoke: (id: string) => Promise<{
@@ -224,24 +277,37 @@ export declare class TarsClient {
224
277
  usage: {
225
278
  input_tokens: number;
226
279
  output_tokens: number;
280
+ cache_read_tokens: number;
281
+ cache_write_tokens: number;
282
+ cost_usd?: number;
283
+ cost_source?: "provider";
284
+ upstream_cost_usd?: number;
227
285
  };
228
286
  }>;
229
287
  };
230
288
  /**
231
- * Which model providers this instance is keyed for (`anthropic`, `openai`,
232
- * `openrouter`, `custom`). Ask before OFFERING models: a picker listing GPT
233
- * models on an instance with no OpenAI key produces turns that all fail at
234
- * the provider call.
289
+ * Which model providers YOU can route now (`anthropic`, `openai`,
290
+ * `openrouter`, `tensorx`, `custom`): those you passed in `providerKeys`,
291
+ * plus the instance's while its cutover fallback is on, plus `custom` on an
292
+ * instance pointed at a local runtime. Ask before OFFERING models: a picker
293
+ * listing GPT models without an OpenAI key produces turns that all fail.
235
294
  */
236
295
  providers(): Promise<string[]>;
296
+ /**
297
+ * Every provider with whether you can route it and on whose key
298
+ * (`request` = your `providerKeys`, `instance` = the deployment's), plus
299
+ * whether the instance still falls back to its own keys. What a settings
300
+ * page shows: all providers, the inactive ones disabled.
301
+ */
302
+ providerStatus(): Promise<ProviderStatusReport>;
237
303
  /**
238
304
  * The models this instance can route AND price — ids are full
239
- * `provider/model` specs. `openrouter/*` entries mirror OpenRouter's live
240
- * catalog, so those ids are guaranteed to exist right now; direct entries
305
+ * `provider/model` specs. `openrouter/*` and `tensorx/*` entries mirror
306
+ * those providers' live catalogs, so those ids exist right now; direct entries
241
307
  * come from the deployment's curated pricing table. Offer models from this
242
308
  * list rather than hardcoding ids that can drift.
243
309
  *
244
- * The list is "what is known now", not "everything routable": `openrouter`
310
+ * The list is "what is known now", not "everything routable": catalog
245
311
  * entries are absent before the instance's first successful catalog
246
312
  * refresh, and a deployment routing only `custom/*` has no priced entries
247
313
  * at all. When it comes back empty, fall back to `providers()` plus your
@@ -276,6 +342,8 @@ export declare class TarsClient {
276
342
  fileId: string;
277
343
  mountPath?: string;
278
344
  }): Promise<{
345
+ ok: boolean;
346
+ file_id: string;
279
347
  mount_path: string;
280
348
  size_bytes: number;
281
349
  }>;
@@ -310,6 +378,8 @@ export declare class TarsSession {
310
378
  after?: number;
311
379
  }): AsyncGenerator<SessionEvent>;
312
380
  mountFile(fileId: string, mountPath?: string): Promise<{
381
+ ok: boolean;
382
+ file_id: string;
313
383
  mount_path: string;
314
384
  size_bytes: number;
315
385
  }>;
@@ -324,6 +394,10 @@ export declare class TarsSession {
324
394
  ok: boolean;
325
395
  interrupted: boolean;
326
396
  }>;
397
+ /** Compact the context now — see `TarsClient.sessions.compact`. */
398
+ compact(opts?: {
399
+ instructions?: string;
400
+ }): Promise<CompactResult>;
327
401
  destroy(): Promise<{
328
402
  ok: boolean;
329
403
  }>;
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './types.js';
2
+ /** The providers `providerKeys` accepts. */
3
+ export const KEYED_PROVIDERS = ['anthropic', 'openai', 'openrouter', 'tensorx'];
2
4
  export class TarsApiError extends Error {
3
5
  status;
4
6
  body;
@@ -14,6 +16,7 @@ export class TarsClient {
14
16
  apiKey;
15
17
  fetchImpl;
16
18
  actAsTenant;
19
+ providerKeyHeaders;
17
20
  constructor(options) {
18
21
  if (!options.apiKey)
19
22
  throw new Error('TarsClient: apiKey is required');
@@ -22,6 +25,13 @@ export class TarsClient {
22
25
  // Bind: an unbound global fetch throws "Illegal invocation" in browsers.
23
26
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
24
27
  this.actAsTenant = options.actAsTenant;
28
+ const unknown = Object.keys(options.providerKeys ?? {}).filter((p) => !KEYED_PROVIDERS.includes(p));
29
+ if (unknown.length > 0) {
30
+ throw new Error(`TarsClient: unknown providerKeys ${unknown.join(', ')} (expected ${KEYED_PROVIDERS.join(', ')})`);
31
+ }
32
+ this.providerKeyHeaders = Object.fromEntries(Object.entries(options.providerKeys ?? {})
33
+ .filter(([, key]) => typeof key === 'string' && key.trim() !== '')
34
+ .map(([provider, key]) => [`x-tars-key-${provider}`, key.trim()]));
25
35
  }
26
36
  // ── low-level HTTP ───────────────────────────────────────────
27
37
  /** Every request path — JSON, uploads, SSE — sends these, so act-as scoping
@@ -30,6 +40,7 @@ export class TarsClient {
30
40
  return {
31
41
  authorization: `Bearer ${this.apiKey}`,
32
42
  ...(this.actAsTenant ? { 'x-tars-tenant': this.actAsTenant } : {}),
43
+ ...this.providerKeyHeaders,
33
44
  };
34
45
  }
35
46
  async request(method, path, body) {
@@ -56,6 +67,8 @@ export class TarsClient {
56
67
  },
57
68
  get: (id) => this.request('GET', `/v1/agents/${id}`),
58
69
  update: (id, patch) => this.request('PATCH', `/v1/agents/${id}`, patch),
70
+ /** Archived agents refuse new sessions (409) and updates; existing
71
+ * sessions keep running. */
59
72
  archive: (id) => this.request('POST', `/v1/agents/${id}/archive`),
60
73
  unarchive: (id) => this.request('POST', `/v1/agents/${id}/unarchive`),
61
74
  versions: async (id) => {
@@ -104,6 +117,22 @@ export class TarsClient {
104
117
  return { sessions: res.sessions, nextCursor: res.next_cursor };
105
118
  },
106
119
  rename: (id, title) => this.request('PATCH', `/v1/sessions/${id}`, { title }),
120
+ /**
121
+ * One page of the session's journal — the same events `get()` inlines,
122
+ * for incremental readers on long sessions. `afterSeq` is the last seq
123
+ * you hold (-1 = from the start); `limit` 1..1000 (default 500).
124
+ * `has_more` says another page follows. Allowed under act-as, unlike the
125
+ * live stream.
126
+ */
127
+ events: async (id, opts) => {
128
+ const params = new URLSearchParams();
129
+ if (opts?.afterSeq !== undefined)
130
+ params.set('after_seq', String(opts.afterSeq));
131
+ if (opts?.limit)
132
+ params.set('limit', String(opts.limit));
133
+ const qs = params.size ? `?${params}` : '';
134
+ return this.request('GET', `/v1/sessions/${id}/events${qs}`);
135
+ },
107
136
  /** Replace the session's extra MCP server set (takes effect next turn;
108
137
  * 409s while a turn is in flight). */
109
138
  setMcpServers: (id, servers) => this.request('PATCH', `/v1/sessions/${id}`, { mcp_servers: servers }),
@@ -120,6 +149,15 @@ export class TarsClient {
120
149
  }),
121
150
  /** Abort the in-flight turn, if any. Idempotent. */
122
151
  interrupt: (id) => this.request('POST', `/v1/sessions/${id}/interrupt`),
152
+ /**
153
+ * Compact the session's context now: the older history is folded into a
154
+ * checkpoint summary and the recent tail kept verbatim — what the
155
+ * platform does on its own as a session nears its model's context
156
+ * window (the `compaction` event either way). Idle sessions only
157
+ * (`turn_in_progress` otherwise). `instructions` is an optional focus
158
+ * for the summary ("keep every SQL statement verbatim").
159
+ */
160
+ compact: (id, opts) => this.request('POST', `/v1/sessions/${id}/compact`, opts ?? {}),
123
161
  usage: (id) => this.request('GET', `/v1/sessions/${id}/usage`),
124
162
  get: (id) => this.request('GET', `/v1/sessions/${id}`),
125
163
  // Re-open an existing session (e.g. after a page refresh or on another
@@ -176,6 +214,8 @@ export class TarsClient {
176
214
  params.set('auth', input.auth);
177
215
  if (input.endUserId)
178
216
  params.set('end_user_id', input.endUserId);
217
+ if (input.schemas)
218
+ params.set('schemas', '1');
179
219
  return this.request('GET', `/v1/mcp-servers/tools?${params}`);
180
220
  },
181
221
  };
@@ -186,7 +226,9 @@ export class TarsClient {
186
226
  list: async () => (await this.request('GET', '/v1/skills')).skills,
187
227
  get: (id) => this.request('GET', `/v1/skills/${id}`),
188
228
  versions: async (id) => (await this.request('GET', `/v1/skills/${id}/versions`)).versions,
189
- /** Upload a new immutable version from zip bytes. */
229
+ /** Upload a new immutable version from zip bytes. `name` / `description`
230
+ * are what SKILL.md's front matter declared (null when absent); `files`
231
+ * counts the bundle's entries. */
190
232
  pushVersion: async (id, zipBytes) => {
191
233
  const res = await this.fetchImpl(`${this.baseUrl}/v1/skills/${id}/versions`, {
192
234
  method: 'POST',
@@ -212,6 +254,7 @@ export class TarsClient {
212
254
  };
213
255
  // ── api keys / identity ──────────────────────────────────────
214
256
  apiKeys = {
257
+ /** Mint another key for this tenant. `key` is returned ONCE. */
215
258
  create: (input) => this.request('POST', '/v1/api-keys', { ...(input?.name ? { name: input.name } : {}) }),
216
259
  list: async () => (await this.request('GET', '/v1/api-keys')).keys,
217
260
  revoke: (id) => this.request('DELETE', `/v1/api-keys/${id}`),
@@ -244,7 +287,14 @@ export class TarsClient {
244
287
  };
245
288
  // ── completions / providers (any key) ────────────────────────
246
289
  // Cheap non-session completion (titles and other one-shots) — routed
247
- // through the same provider seam as agent turns.
290
+ // through the same provider seam as agent turns, on this client's
291
+ // `providerKeys` (400 `provider_not_configured` when the model's provider
292
+ // is not routable). `maxTokens` defaults to 256 and is capped at 1024;
293
+ // `model` defaults to `openrouter/anthropic/claude-haiku-4-5`. In `usage`,
294
+ // the cache counts make a caching claim checkable from outside; `cost_usd`
295
+ // is present only when the provider reported a bill (`cost_source:
296
+ // 'provider'`), and `upstream_cost_usd`, under BYOK through OpenRouter, is
297
+ // what the vendor charged the key's owner.
248
298
  completions = {
249
299
  create: (input) => this.request('POST', '/v1/completions', {
250
300
  prompt: input.prompt,
@@ -254,23 +304,33 @@ export class TarsClient {
254
304
  }),
255
305
  };
256
306
  /**
257
- * Which model providers this instance is keyed for (`anthropic`, `openai`,
258
- * `openrouter`, `custom`). Ask before OFFERING models: a picker listing GPT
259
- * models on an instance with no OpenAI key produces turns that all fail at
260
- * the provider call.
307
+ * Which model providers YOU can route now (`anthropic`, `openai`,
308
+ * `openrouter`, `tensorx`, `custom`): those you passed in `providerKeys`,
309
+ * plus the instance's while its cutover fallback is on, plus `custom` on an
310
+ * instance pointed at a local runtime. Ask before OFFERING models: a picker
311
+ * listing GPT models without an OpenAI key produces turns that all fail.
261
312
  */
262
313
  async providers() {
263
314
  const body = await this.request('GET', '/v1/providers');
264
315
  return body.providers ?? [];
265
316
  }
317
+ /**
318
+ * Every provider with whether you can route it and on whose key
319
+ * (`request` = your `providerKeys`, `instance` = the deployment's), plus
320
+ * whether the instance still falls back to its own keys. What a settings
321
+ * page shows: all providers, the inactive ones disabled.
322
+ */
323
+ async providerStatus() {
324
+ return this.request('GET', '/v1/providers/status');
325
+ }
266
326
  /**
267
327
  * The models this instance can route AND price — ids are full
268
- * `provider/model` specs. `openrouter/*` entries mirror OpenRouter's live
269
- * catalog, so those ids are guaranteed to exist right now; direct entries
328
+ * `provider/model` specs. `openrouter/*` and `tensorx/*` entries mirror
329
+ * those providers' live catalogs, so those ids exist right now; direct entries
270
330
  * come from the deployment's curated pricing table. Offer models from this
271
331
  * list rather than hardcoding ids that can drift.
272
332
  *
273
- * The list is "what is known now", not "everything routable": `openrouter`
333
+ * The list is "what is known now", not "everything routable": catalog
274
334
  * entries are absent before the instance's first successful catalog
275
335
  * refresh, and a deployment routing only `custom/*` has no priced entries
276
336
  * at all. When it comes back empty, fall back to `providers()` plus your
@@ -508,6 +568,10 @@ export class TarsSession {
508
568
  interrupt() {
509
569
  return this.client.sessions.interrupt(this.info.id);
510
570
  }
571
+ /** Compact the context now — see `TarsClient.sessions.compact`. */
572
+ compact(opts) {
573
+ return this.client.sessions.compact(this.info.id, opts);
574
+ }
511
575
  destroy() {
512
576
  return this.client.sessions.destroy(this.info.id);
513
577
  }
package/dist/types.d.ts CHANGED
@@ -67,6 +67,12 @@ export type SessionEvent = {
67
67
  } & ({
68
68
  status: 'connected';
69
69
  tool_count: number;
70
+ /** Estimated tokens this server's kept tool definitions (names,
71
+ * descriptions, input schemas) add to every request of the turn,
72
+ * at ~4 chars/token — size connectors against each other and
73
+ * against the rest of the prompt. Absent on rows journaled
74
+ * before 0.3.0. */
75
+ schema_tokens?: number;
70
76
  /** The tool names the model received from this server (its own
71
77
  * unprefixed names, in server order) — what `tool_count` counts.
72
78
  * Absent on rows journaled by servers before 0.2.4. */
@@ -90,7 +96,44 @@ export type SessionEvent = {
90
96
  message: string;
91
97
  server?: string;
92
98
  retry_status: 'terminal' | 'exhausted';
99
+ }
100
+ /**
101
+ * Context compaction: the older history was folded into a summary
102
+ * checkpoint and the recent tail kept verbatim, so the session stays under
103
+ * the model's context window. Nested discriminant on `status`, like
104
+ * mcp_connect: `started` opens the (possibly long) summarization,
105
+ * `completed` carries the checkpoint the model now runs on, `failed` says
106
+ * the history is unchanged. `reason`: `auto` = the threshold check before
107
+ * a model call, `overflow` = the provider rejected the prompt as too long,
108
+ * `manual` = `sessions.compact()`.
109
+ */
110
+ | ({
111
+ type: 'compaction';
112
+ reason: CompactionReason;
113
+ } & ({
114
+ status: 'started';
93
115
  } | {
116
+ status: 'completed';
117
+ /** The checkpoint text the model sees in place of the summarized
118
+ * messages — the tenant-visible record of what was kept. */
119
+ summary: string;
120
+ /** Estimated prompt tokens before and after (chars/4 over the
121
+ * history; the provider's own count lands on the next turn_end). */
122
+ tokens_before: number;
123
+ tokens_after: number;
124
+ /** How many history messages the summary replaced, and how many
125
+ * recent ones stayed verbatim after it. */
126
+ summarized_messages: number;
127
+ kept_messages: number;
128
+ /** The model that wrote the summary; `fallback` when the
129
+ * summarizer failed during overflow recovery and a deterministic
130
+ * checkpoint (the dropped requests, listed) was written instead. */
131
+ model: string;
132
+ } | {
133
+ status: 'failed';
134
+ code: AgentErrorCode;
135
+ message: string;
136
+ })) | {
94
137
  type: 'turn_end';
95
138
  reason: 'completed' | 'interrupted' | 'error';
96
139
  usage?: {
@@ -102,13 +145,39 @@ export type SessionEvent = {
102
145
  cacheReadTokens?: number;
103
146
  /** Input tokens written to the provider's prompt cache. */
104
147
  cacheWriteTokens?: number;
148
+ /** The LAST model step's full prompt size (uncached + cache read +
149
+ * cache write) as the provider counted it — what the next turn
150
+ * starts from, and the exact figure for a context gauge. The
151
+ * buckets above are SUMS over the turn's steps. Absent on turns
152
+ * that ended before a step completed. */
153
+ contextTokens?: number;
105
154
  };
106
155
  });
156
+ /** Why a compaction ran — see the `compaction` event. */
157
+ export type CompactionReason = 'auto' | 'overflow' | 'manual';
158
+ /** `sessions.compact()` — the no-op success (`compacted: false`) means the
159
+ * history was too short to fold; a summarizer failure is a thrown
160
+ * `TarsApiError` (502, code `compaction_failed`) and leaves the history as
161
+ * is. */
162
+ export type CompactResult = {
163
+ ok: true;
164
+ compacted: true;
165
+ /** Estimated prompt tokens before and after (chars/4 over the history). */
166
+ tokens_before: number;
167
+ tokens_after: number;
168
+ summarized_messages: number;
169
+ kept_messages: number;
170
+ } | {
171
+ ok: true;
172
+ compacted: false;
173
+ reason: 'nothing_to_compact';
174
+ };
107
175
  export type BuiltInTool = 'bash' | 'read_file' | 'write_file' | 'edit_file' | 'glob' | 'grep' | 'web_fetch';
108
176
  /**
109
177
  * Extended-thinking depth. Provider-neutral: TARS maps it to Anthropic's
110
- * `effort` (with adaptive thinking) or OpenRouter's `reasoning.effort`
111
- * (`max` → `xhigh`). Omit for the provider default. The reasoning text itself
178
+ * `effort` (with adaptive thinking), OpenRouter's `reasoning.effort`
179
+ * (`max` → `xhigh`), or TensorX's `reasoning_effort` (`xhigh`/`max` →
180
+ * `high`; models there may ignore it). Omit for the provider default. The reasoning text itself
112
181
  * is never streamed or journaled — only its depth is configurable.
113
182
  */
114
183
  export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
@@ -177,6 +246,9 @@ export interface Skill {
177
246
  display_title: string;
178
247
  latest_version: number;
179
248
  created_at: string;
249
+ /** Present on `get()` / `list()`; the `create()` echo omits both. */
250
+ client_id?: string;
251
+ updated_at?: string;
180
252
  }
181
253
  export interface SkillVersion {
182
254
  version: number;
@@ -185,6 +257,24 @@ export interface SkillVersion {
185
257
  size_bytes: number;
186
258
  created_at: string;
187
259
  }
260
+ /** Providers a client brings its own key for (`TarsClientOptions.providerKeys`). */
261
+ export type KeyedProvider = 'anthropic' | 'openai' | 'openrouter' | 'tensorx';
262
+ /** Every routable provider; `custom` is configured per instance, never per client. */
263
+ export type ProviderName = KeyedProvider | 'custom';
264
+ export type ProviderKeys = Partial<Record<KeyedProvider, string>>;
265
+ export interface ProviderStatus {
266
+ provider: ProviderName;
267
+ /** Can this client route a model on it right now? */
268
+ active: boolean;
269
+ /** Whose key would route it: yours (`request`), the instance's, or none. */
270
+ source: 'request' | 'instance' | null;
271
+ }
272
+ export interface ProviderStatusReport {
273
+ providers: ProviderStatus[];
274
+ /** True while the instance still routes providers you sent no key for on
275
+ * its own keys (the cutover fallback). */
276
+ instance_fallback: boolean;
277
+ }
188
278
  /** One routable-and-priced model from `GET /v1/models`. Prices are USD per
189
279
  * million tokens; `id` is the full `provider/model` spec, usable as-is when
190
280
  * creating agents/sessions. The list is "what is known now" — see
@@ -192,6 +282,9 @@ export interface SkillVersion {
192
282
  export interface ModelInfo {
193
283
  id: string;
194
284
  name: string;
285
+ /** Context window in tokens, when the provider's catalog publishes one
286
+ * (OpenRouter, TensorX). Absent for the direct routes. */
287
+ context_window?: number;
195
288
  pricing: {
196
289
  input: number;
197
290
  output: number;
@@ -274,6 +367,9 @@ export interface McpCredentialStatus {
274
367
  auth_type: 'bearer' | 'oauth';
275
368
  expires_at: string | null;
276
369
  updated_at: string;
370
+ /** Can TARS renew this credential itself? `false` on an oauth credential
371
+ * means it will dead-end at `connect_required` when it expires. */
372
+ refreshable: boolean;
277
373
  }
278
374
  /** How `mcp.listTools` names the server: the same fields a declaration
279
375
  * carries (so the credential lookup and the dial match a turn's), plus whose
@@ -286,12 +382,34 @@ export interface ListMcpToolsInput {
286
382
  /** Resolve this end user's credential first, then the tenant-level one;
287
383
  * omit for tenant-level only. Only read when `auth` is `'required'`. */
288
384
  endUserId?: string;
385
+ /** Also return each tool's `input_schema` and its FULL description — the
386
+ * exact definition the model is shown, for weighing a connector's prompt
387
+ * cost per tool. Off (default): picker shape, descriptions capped. */
388
+ schemas?: boolean;
289
389
  }
290
390
  /** One tool as the server lists it. `description` is capped at 500
291
- * characters (an ellipsis marks a cut); `null` when the server gave none. */
391
+ * characters (an ellipsis marks a cut) unless `schemas` was requested;
392
+ * `null` when the server gave none. */
393
+ /** The MCP spec's tool annotations, as the server sent them (these five
394
+ * keys only). Hints for grouping and labelling tools in a picker, never
395
+ * guarantees: a policy (`tools` / `blocked_tools`) must not trust them. */
396
+ export interface McpToolAnnotations {
397
+ title?: string;
398
+ readOnlyHint?: boolean;
399
+ destructiveHint?: boolean;
400
+ idempotentHint?: boolean;
401
+ openWorldHint?: boolean;
402
+ }
292
403
  export interface McpToolInfo {
293
404
  name: string;
294
405
  description: string | null;
406
+ /** Human-readable name: the tool's `title`, else `annotations.title`,
407
+ * else null when the server gave neither. */
408
+ title: string | null;
409
+ /** The server's annotations; null when it sent none. */
410
+ annotations: McpToolAnnotations | null;
411
+ /** The input JSON Schema as listed — present only with `schemas: true`. */
412
+ input_schema?: Record<string, unknown>;
295
413
  }
296
414
  export interface McpServerTools {
297
415
  server: string;
@@ -337,9 +455,12 @@ export interface SessionClientTool {
337
455
  * given turn is in that turn's `mcp_connect` event (`tools` / `filtered_out`).
338
456
  */
339
457
  export interface SessionToolSurface {
458
+ /** `input_schema`: the JSON Schema (draft-7) the model is shown for the
459
+ * tool — size it to know what the built-ins cost in context. */
340
460
  builtin: Array<{
341
461
  name: BuiltInTool;
342
462
  description: string;
463
+ input_schema: Record<string, unknown>;
343
464
  }>;
344
465
  client: SessionClientTool[];
345
466
  mcp_servers: McpServer[];
@@ -459,8 +580,12 @@ export interface DailyUsageRow {
459
580
  }
460
581
  export interface SessionListItem {
461
582
  id: string;
583
+ /** A `SessionStatus` value (typed loosely for compatibility). */
462
584
  status: string;
463
585
  turn_state: TurnState;
586
+ /** Only meaningful with `includeArchived`; absent from servers predating
587
+ * this field. */
588
+ archived?: boolean;
464
589
  model: string;
465
590
  agent_id: string | null;
466
591
  end_user_id: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wice24/tars-sdk",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "Client SDK for TARS — the self-hosted managed-agents platform (sessions, streaming turns, client tools, MCP credentials, skills, files, usage, tenant administration)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,13 +12,6 @@
12
12
  "files": [
13
13
  "dist"
14
14
  ],
15
- "scripts": {
16
- "build": "tsc -p tsconfig.build.json",
17
- "typecheck": "tsc --noEmit",
18
- "test": "vitest run",
19
- "test:watch": "vitest",
20
- "test:coverage": "vitest run --coverage"
21
- },
22
15
  "devDependencies": {
23
16
  "typescript": "^6.0.3",
24
17
  "@types/node": "^26.1.0",
@@ -40,5 +33,12 @@
40
33
  "mcp",
41
34
  "anthropic",
42
35
  "sdk"
43
- ]
44
- }
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc -p tsconfig.build.json",
39
+ "typecheck": "tsc --noEmit",
40
+ "test": "vitest run",
41
+ "test:watch": "vitest",
42
+ "test:coverage": "vitest run --coverage"
43
+ }
44
+ }