@wice24/tars-sdk 0.1.7 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Agent, AgentVersion, ClientTool, CreateAgentInput, FileMeta, McpCredentialInput, McpCredentialStatus, SessionEvent, SessionInfo, SessionListItem, UpdateAgentInput, UploadFileInput, UsageSummary, SessionUsageRow, DailyUsageRow, McpServer, Skill, SkillVersion, ApiKeyInfo, ClientIdentity, ClientRole, PlatformStats, Tenant } from './types.js';
1
+ import type { Agent, AgentVersion, ClientTool, CreateAgentInput, FileMeta, McpCredentialInput, McpCredentialStatus, ReasoningConfig, SessionEvent, ModelInfo, 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
4
  export declare class TarsApiError extends Error {
@@ -11,12 +11,28 @@ export interface TarsClientOptions {
11
11
  baseUrl?: string;
12
12
  /** Injectable fetch (tests, custom agents/proxies). Defaults to global fetch. */
13
13
  fetchImpl?: typeof globalThis.fetch;
14
+ /**
15
+ * Admin keys only: scope every request to this tenant id (sent as the
16
+ * `X-Tars-Tenant` header). Act-as is READ-ONLY — GETs return the target
17
+ * 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
21
+ * 404s on every call; an empty value 400s. The effective role is always
22
+ * `tenant`, so `admin.*` calls under act-as also get 403 `admin_required`
23
+ * — use a client WITHOUT this option to administrate.
24
+ */
25
+ actAsTenant?: string;
14
26
  }
15
27
  export declare class TarsClient {
16
28
  private readonly baseUrl;
17
29
  private readonly apiKey;
18
30
  private readonly fetchImpl;
31
+ private readonly actAsTenant?;
19
32
  constructor(options: TarsClientOptions);
33
+ /** Every request path — JSON, uploads, SSE — sends these, so act-as scoping
34
+ * can never silently miss a surface. */
35
+ private authHeaders;
20
36
  private request;
21
37
  readonly agents: {
22
38
  create: (input: CreateAgentInput) => Promise<Agent>;
@@ -47,6 +63,8 @@ export declare class TarsClient {
47
63
  /** Appended to the agent's system prompt for this session only. */
48
64
  systemSuffix?: string;
49
65
  tools?: ClientTool[];
66
+ /** Per-session thinking-depth override (mirrors `model`). */
67
+ reasoning?: ReasoningConfig;
50
68
  }) => Promise<TarsSession>;
51
69
  list: (opts?: {
52
70
  limit?: number;
@@ -202,6 +220,20 @@ export declare class TarsClient {
202
220
  * the provider call.
203
221
  */
204
222
  providers(): Promise<string[]>;
223
+ /**
224
+ * The models this instance can route AND price — ids are full
225
+ * `provider/model` specs. `openrouter/*` entries mirror OpenRouter's live
226
+ * catalog, so those ids are guaranteed to exist right now; direct entries
227
+ * come from the deployment's curated pricing table. Offer models from this
228
+ * list rather than hardcoding ids that can drift.
229
+ *
230
+ * The list is "what is known now", not "everything routable": `openrouter`
231
+ * entries are absent before the instance's first successful catalog
232
+ * refresh, and a deployment routing only `custom/*` has no priced entries
233
+ * at all. When it comes back empty, fall back to `providers()` plus your
234
+ * own lineup instead of rendering an empty picker.
235
+ */
236
+ models(): Promise<ModelInfo[]>;
205
237
  readonly usage: {
206
238
  get: (filter?: {
207
239
  from?: string | Date;
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export class TarsClient {
13
13
  baseUrl;
14
14
  apiKey;
15
15
  fetchImpl;
16
+ actAsTenant;
16
17
  constructor(options) {
17
18
  if (!options.apiKey)
18
19
  throw new Error('TarsClient: apiKey is required');
@@ -20,13 +21,22 @@ export class TarsClient {
20
21
  this.baseUrl = (options.baseUrl ?? 'http://localhost:3001').replace(/\/$/, '');
21
22
  // Bind: an unbound global fetch throws "Illegal invocation" in browsers.
22
23
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
24
+ this.actAsTenant = options.actAsTenant;
23
25
  }
24
26
  // ── low-level HTTP ───────────────────────────────────────────
27
+ /** Every request path — JSON, uploads, SSE — sends these, so act-as scoping
28
+ * can never silently miss a surface. */
29
+ authHeaders() {
30
+ return {
31
+ authorization: `Bearer ${this.apiKey}`,
32
+ ...(this.actAsTenant ? { 'x-tars-tenant': this.actAsTenant } : {}),
33
+ };
34
+ }
25
35
  async request(method, path, body) {
26
36
  const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
27
37
  method,
28
38
  headers: {
29
- authorization: `Bearer ${this.apiKey}`,
39
+ ...this.authHeaders(),
30
40
  ...(body !== undefined ? { 'content-type': 'application/json' } : {}),
31
41
  },
32
42
  ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
@@ -64,6 +74,7 @@ export class TarsClient {
64
74
  ...(input.metadata ? { metadata: input.metadata } : {}),
65
75
  ...(input.mcpServers?.length ? { mcp_servers: input.mcpServers } : {}),
66
76
  ...(input.systemSuffix ? { system_suffix: input.systemSuffix } : {}),
77
+ ...(input.reasoning ? { reasoning: input.reasoning } : {}),
67
78
  ...(input.tools?.length
68
79
  ? {
69
80
  custom_tools: input.tools.map((t) => ({
@@ -155,7 +166,7 @@ export class TarsClient {
155
166
  pushVersion: async (id, zipBytes) => {
156
167
  const res = await this.fetchImpl(`${this.baseUrl}/v1/skills/${id}/versions`, {
157
168
  method: 'POST',
158
- headers: { authorization: `Bearer ${this.apiKey}`, 'content-type': 'application/zip' },
169
+ headers: { ...this.authHeaders(), 'content-type': 'application/zip' },
159
170
  body: zipBytes,
160
171
  });
161
172
  if (!res.ok)
@@ -168,7 +179,7 @@ export class TarsClient {
168
179
  */
169
180
  downloadVersion: async (id, version) => {
170
181
  const res = await this.fetchImpl(`${this.baseUrl}/v1/skills/${id}/versions/${version}/content`, {
171
- headers: { authorization: `Bearer ${this.apiKey}` },
182
+ headers: this.authHeaders(),
172
183
  });
173
184
  if (!res.ok)
174
185
  throw new TarsApiError(res.status, await res.json().catch(() => undefined));
@@ -228,6 +239,23 @@ export class TarsClient {
228
239
  const body = await this.request('GET', '/v1/providers');
229
240
  return body.providers ?? [];
230
241
  }
242
+ /**
243
+ * The models this instance can route AND price — ids are full
244
+ * `provider/model` specs. `openrouter/*` entries mirror OpenRouter's live
245
+ * catalog, so those ids are guaranteed to exist right now; direct entries
246
+ * come from the deployment's curated pricing table. Offer models from this
247
+ * list rather than hardcoding ids that can drift.
248
+ *
249
+ * The list is "what is known now", not "everything routable": `openrouter`
250
+ * entries are absent before the instance's first successful catalog
251
+ * refresh, and a deployment routing only `custom/*` has no priced entries
252
+ * at all. When it comes back empty, fall back to `providers()` plus your
253
+ * own lineup instead of rendering an empty picker.
254
+ */
255
+ async models() {
256
+ const body = await this.request('GET', '/v1/models');
257
+ return body.models ?? [];
258
+ }
231
259
  // ── usage ────────────────────────────────────────────────────
232
260
  usage = {
233
261
  // Aggregated token/cost usage for this tenant, with per-model, per-agent,
@@ -272,7 +300,7 @@ export class TarsClient {
272
300
  const res = await this.fetchImpl(`${this.baseUrl}/v1/files?name=${encodeURIComponent(input.name)}`, {
273
301
  method: 'POST',
274
302
  headers: {
275
- authorization: `Bearer ${this.apiKey}`,
303
+ ...this.authHeaders(),
276
304
  'content-type': input.mime ?? 'application/octet-stream',
277
305
  },
278
306
  body: bytes,
@@ -298,7 +326,7 @@ export class TarsClient {
298
326
  get: (id) => this.request('GET', `/v1/files/${id}`),
299
327
  download: async (id) => {
300
328
  const res = await this.fetchImpl(`${this.baseUrl}/v1/files/${id}/content`, {
301
- headers: { authorization: `Bearer ${this.apiKey}` },
329
+ headers: this.authHeaders(),
302
330
  });
303
331
  if (!res.ok || !res.body) {
304
332
  throw new TarsApiError(res.status, await res.json().catch(() => undefined));
@@ -339,7 +367,7 @@ export class TarsClient {
339
367
  const res = await this.fetchImpl(`${this.baseUrl}/v1/sessions/${sessionId}/messages`, {
340
368
  method: 'POST',
341
369
  headers: {
342
- authorization: `Bearer ${this.apiKey}`,
370
+ ...this.authHeaders(),
343
371
  'content-type': 'application/json',
344
372
  },
345
373
  body: JSON.stringify({ text }),
@@ -356,7 +384,7 @@ export class TarsClient {
356
384
  async streamEvents(sessionId, after, opts) {
357
385
  const qs = `after=${after}` + (opts?.mode ? `&mode=${opts.mode}` : '');
358
386
  const res = await this.fetchImpl(`${this.baseUrl}/v1/sessions/${sessionId}/stream?${qs}`, {
359
- headers: { authorization: `Bearer ${this.apiKey}` },
387
+ headers: this.authHeaders(),
360
388
  });
361
389
  if (!res.ok || !res.body) {
362
390
  const payload = await res.json().catch(() => undefined);
package/dist/types.d.ts CHANGED
@@ -57,7 +57,23 @@ export type SessionEvent = {
57
57
  } | {
58
58
  type: 'connect_required';
59
59
  server: string;
60
+ }
61
+ /** Every MCP dial's OUTCOME, successes included (one per server per turn):
62
+ * `connect_required`/`error` cover only the failure paths — this is the
63
+ * diagnostic twin that makes a healthy connector visible. */
64
+ | ({
65
+ type: 'mcp_connect';
66
+ server: string;
67
+ } & ({
68
+ status: 'connected';
69
+ tool_count: number;
70
+ } | {
71
+ status: 'failed';
72
+ code: AgentErrorCode;
73
+ message: string;
60
74
  } | {
75
+ status: 'missing_credential';
76
+ })) | {
61
77
  type: 'error';
62
78
  code: AgentErrorCode;
63
79
  message: string;
@@ -73,7 +89,17 @@ export type SessionEvent = {
73
89
  cacheWriteTokens?: number;
74
90
  };
75
91
  });
76
- export type BuiltInTool = 'bash' | 'read_file' | 'write_file';
92
+ export type BuiltInTool = 'bash' | 'read_file' | 'write_file' | 'edit_file' | 'glob' | 'grep' | 'web_fetch';
93
+ /**
94
+ * Extended-thinking depth. Provider-neutral: TARS maps it to Anthropic's
95
+ * `effort` (with adaptive thinking) or OpenRouter's `reasoning.effort`
96
+ * (`max` → `xhigh`). Omit for the provider default. The reasoning text itself
97
+ * is never streamed or journaled — only its depth is configurable.
98
+ */
99
+ export type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
100
+ export interface ReasoningConfig {
101
+ effort: ReasoningEffort;
102
+ }
77
103
  export interface McpServer {
78
104
  name: string;
79
105
  url: string;
@@ -112,6 +138,7 @@ export interface Agent {
112
138
  mcp_servers: McpServer[] | null;
113
139
  skills: SkillPin[] | null;
114
140
  environment: AgentEnvironment | null;
141
+ reasoning: ReasoningConfig | null;
115
142
  created_at: string;
116
143
  updated_at: string;
117
144
  }
@@ -129,6 +156,24 @@ export interface SkillVersion {
129
156
  size_bytes: number;
130
157
  created_at: string;
131
158
  }
159
+ /** One routable-and-priced model from `GET /v1/models`. Prices are USD per
160
+ * million tokens; `id` is the full `provider/model` spec, usable as-is when
161
+ * creating agents/sessions. The list is "what is known now" — see
162
+ * `TarsClient.models()` for the empty/partial cases. */
163
+ export interface ModelInfo {
164
+ id: string;
165
+ name: string;
166
+ pricing: {
167
+ input: number;
168
+ output: number;
169
+ /** Absent ≠ free: TARS bills an unpublished cache-read price at the
170
+ * `input` rate (conservative upper bound). */
171
+ cache_read?: number;
172
+ /** Absent ≠ free: billed at the `input` rate when unpublished. Providers
173
+ * with genuinely free cache writes report an explicit 0. */
174
+ cache_write?: number;
175
+ };
176
+ }
132
177
  export interface ApiKeyInfo {
133
178
  id: string;
134
179
  key_prefix: string;
@@ -214,6 +259,7 @@ export interface AgentVersion {
214
259
  export interface CreateAgentInput {
215
260
  skills?: SkillPin[];
216
261
  environment?: AgentEnvironment;
262
+ reasoning?: ReasoningConfig | null;
217
263
  name: string;
218
264
  system: string;
219
265
  model?: string;
@@ -242,6 +288,25 @@ export interface SessionInfo {
242
288
  input_schema: Record<string, unknown>;
243
289
  timeout_sec?: number;
244
290
  }> | null;
291
+ /**
292
+ * The EFFECTIVE system prompt the session's turns run with — agent base
293
+ * prompt + session system_suffix + skills appendix, fused at creation.
294
+ * Optional: absent from servers predating this field.
295
+ */
296
+ system_prompt?: string;
297
+ /**
298
+ * The EFFECTIVE thinking depth the session's turns run with (the agent's,
299
+ * or the per-session override). null = provider default; absent from
300
+ * servers predating this field.
301
+ */
302
+ reasoning?: ReasoningConfig | null;
303
+ /**
304
+ * The un-merged per-session suffix, when a non-empty one was passed at
305
+ * creation. Also null on sessions created before this release: the audit
306
+ * column went unwritten historically (the suffix is still fused into
307
+ * system_prompt there), and old rows cannot be backfilled.
308
+ */
309
+ system_suffix?: string | null;
245
310
  archived?: boolean;
246
311
  last_seq?: number;
247
312
  events?: SessionEvent[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wice24/tars-sdk",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
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": {