agent-accelerator 0.4.0 → 0.4.5

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.
@@ -7,6 +7,7 @@ import type {
7
7
  } from "./core.ts";
8
8
  import type { ModelProviderInstance } from "../providers/registry.ts";
9
9
  import type { Agent } from "../agent/agent.ts";
10
+ import type { z } from "zod";
10
11
 
11
12
  // ---------------------------------------------------------------------------
12
13
  // Q-52/Q-53/Q-54 canonical option types (all additive; nothing renamed).
@@ -61,6 +62,17 @@ export type ToolChoiceOption =
61
62
  /** Canonical stop control (Q-54): sequence(s) halting generation. */
62
63
  export type StopOption = string | string[];
63
64
 
65
+ /** Canonical structured-output input (Q-61). Zod, JSON Schema, or explicit form. */
66
+ export type StructuredOutputOption =
67
+ | z.ZodType
68
+ | Record<string, unknown>
69
+ | {
70
+ name?: string;
71
+ description?: string;
72
+ schema: z.ZodType | Record<string, unknown>;
73
+ strict?: boolean;
74
+ };
75
+
64
76
  /** Configuration used to construct an {@link Agent}. */
65
77
  export interface AgentConfig {
66
78
  /** Display name used in tool and sub-agent metadata. */
@@ -142,9 +154,12 @@ export interface AgentConfig {
142
154
  /** Deterministic seed, when the provider supports it. */
143
155
  seed?: number;
144
156
  /**
145
- * Optional output schema (Zod or JSON Schema, stored as `unknown` so `zod`
157
+ * Optional output schema (Zod, JSON Schema, or `{ name, schema, strict }`,
158
+ * see `StructuredOutputOption`, stored as `unknown` so `zod`
146
159
  * stays optional for type-only consumers). Agent-level only; per-run
147
- * overrides are intentionally unsupported.
160
+ * overrides are intentionally unsupported. When set, the model answer is
161
+ * constrained to the schema on all providers and `AgentResponse.parsed`
162
+ * carries the validated value.
148
163
  */
149
164
  output?: unknown;
150
165
  /** Retry policy for provider calls. */
@@ -87,6 +87,7 @@ export interface ModelCapabilities {
87
87
  supportsLongCacheRetention?: boolean;
88
88
  supportsParallelToolCalls?: boolean;
89
89
  supportsStreaming?: boolean;
90
+ supportsStructuredOutput?: boolean;
90
91
  modalities?: ("text" | "image" | "audio" | "video" | "pdf")[];
91
92
  // derived
92
93
  supportsReasoningToggle?: boolean;
@@ -138,6 +139,24 @@ export interface ModelSpec {
138
139
  raw?: RawModelData;
139
140
  }
140
141
 
142
+ // ---------------------------------------------------------------------------
143
+ // Structured outputs — normalized, provider-agnostic spec.
144
+ // Canonical input is `AgentConfig.output` (Zod, JSON Schema, or explicit
145
+ // `{ name, schema, strict }`); `normalizeStructuredOutput` in
146
+ // `src/providers.ts` produces this shape for adapters.
147
+ // ---------------------------------------------------------------------------
148
+ /** Normalized structured-output request passed from Agent to providers. */
149
+ export interface StructuredOutputSpec {
150
+ /** Format name (1-64 chars, `[a-zA-Z0-9_-]`). Defaults to `structured_output`. */
151
+ name: string;
152
+ /** JSON Schema for the final answer (already converted from Zod when needed). */
153
+ schema: Record<string, unknown>;
154
+ /** Strict schema adherence (OpenAI/OpenRouter only; ignored by Google). Defaults to true. */
155
+ strict?: boolean;
156
+ /** Optional description of what the format is for. */
157
+ description?: string;
158
+ }
159
+
141
160
  // ---------------------------------------------------------------------------
142
161
  // Provider plumbing — unchanged API
143
162
  // ---------------------------------------------------------------------------
@@ -156,6 +175,8 @@ export interface ProviderRequestOptions {
156
175
  env?: Record<string, string>;
157
176
  maxRetries?: number;
158
177
  maxRetryDelayMs?: number;
178
+ /** Normalized structured-output spec (see `normalizeStructuredOutput`). */
179
+ output?: StructuredOutputSpec;
159
180
  }
160
181
 
161
182
  /** Auditable request/response wire payload captured in AgentResponse. */
@@ -101,6 +101,8 @@ export function normalizeFinishReason(raw?: string): {
101
101
  /** JSON representation returned by AgentResponse.toJSON(). */
102
102
  export interface AgentResponseJSON {
103
103
  text: string;
104
+ /** Validated structured-output value when `AgentConfig.output` was set. */
105
+ parsed?: unknown;
104
106
  thinking?: string;
105
107
  thoughtSignature?: string;
106
108
  toolCalls?: ToolCallRecord[];
@@ -121,6 +123,8 @@ export interface AgentResponseJSON {
121
123
  /** Normalized final result returned by every Agent run. */
122
124
  export class AgentResponse {
123
125
  readonly text: string;
126
+ /** Validated structured-output value when `AgentConfig.output` was set. */
127
+ readonly parsed?: unknown;
124
128
  readonly thinking?: string;
125
129
  readonly thoughtSignature?: string;
126
130
  readonly toolCalls: ToolCallRecord[];
@@ -146,6 +150,7 @@ export class AgentResponse {
146
150
  */
147
151
  constructor(data: {
148
152
  text: string;
153
+ parsed?: unknown;
149
154
  thinking?: string;
150
155
  thoughtSignature?: string;
151
156
  toolCalls?: ToolCallRecord[];
@@ -162,6 +167,7 @@ export class AgentResponse {
162
167
  turns?: number;
163
168
  }) {
164
169
  this.text = data.text;
170
+ if (data.parsed !== undefined) (this as { parsed?: unknown }).parsed = data.parsed;
165
171
  this.thinking = data.thinking;
166
172
  this.thoughtSignature = data.thoughtSignature;
167
173
  this.toolCalls = data.toolCalls ?? [];
@@ -187,6 +193,7 @@ export class AgentResponse {
187
193
  toJSON(): AgentResponseJSON {
188
194
  return {
189
195
  text: this.text,
196
+ ...(this.parsed !== undefined ? { parsed: this.parsed } : {}),
190
197
  thinking: this.thinking,
191
198
  thoughtSignature: this.thoughtSignature,
192
199
  toolCalls: this.toolCalls.length > 0 ? this.toolCalls : undefined,
package/src/utils/env.ts CHANGED
@@ -9,7 +9,10 @@
9
9
  */
10
10
  export function getEnv(key: string, fallback?: string): string | undefined {
11
11
  if (typeof process !== "undefined" && process.env && process.env[key]) {
12
- return process.env[key];
12
+ // Trim quotes/whitespace from .env files (`sk-..."\n` -> 401 otherwise).
13
+ const raw = String(process.env[key]);
14
+ const trimmed = raw.trim().replace(/^["']|["']$/g, "").trim();
15
+ if (trimmed) return trimmed;
13
16
  }
14
17
  return fallback;
15
18
  }
@@ -22,20 +25,31 @@ export type ProviderEnv = Record<string, string>;
22
25
  * @example `const key = getApiKey("google");`
23
26
  */
24
27
  export function getApiKey(provider: string, explicitKey?: string, env?: ProviderEnv): string | undefined {
25
- if (explicitKey) return explicitKey;
28
+ const clean = (v: unknown): string | undefined => {
29
+ if (typeof v !== "string") return undefined;
30
+ const t = v.trim().replace(/^["']|["']$/g, "").trim();
31
+ return t || undefined;
32
+ };
33
+ const cleanedExplicit = clean(explicitKey);
34
+ if (cleanedExplicit) return cleanedExplicit;
26
35
  // ProviderEnv override takes precedence (agent-accel inspiration, SDK-light)
27
36
  if (env) {
28
37
  const upper = `${provider.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_API_KEY`;
29
- if (env[upper]) return env[upper];
38
+ const fromEnv = clean(env[upper]);
39
+ if (fromEnv) return fromEnv;
30
40
  // google aliases
31
41
  if (provider.toLowerCase().startsWith("google") || provider.toLowerCase() === "gemini") {
32
- if (env["GEMINI_API_KEY"]) return env["GEMINI_API_KEY"];
33
- if (env["GOOGLE_API_KEY"]) return env["GOOGLE_API_KEY"];
42
+ const g1 = clean(env["GEMINI_API_KEY"]);
43
+ if (g1) return g1;
44
+ const g2 = clean(env["GOOGLE_API_KEY"]);
45
+ if (g2) return g2;
34
46
  }
35
47
  // openai aliases
36
48
  if (provider.toLowerCase() === "openai") {
37
- if (env["OPENAI_BASE_API_KEY"]) return env["OPENAI_BASE_API_KEY"];
38
- if (env["OPENAI_API_KEY"]) return env["OPENAI_API_KEY"];
49
+ const o1 = clean(env["OPENAI_BASE_API_KEY"]);
50
+ if (o1) return o1;
51
+ const o2 = clean(env["OPENAI_API_KEY"]);
52
+ if (o2) return o2;
39
53
  }
40
54
  }
41
55
 
@@ -54,7 +54,18 @@ export function toConciseProviderError(error: unknown, providerId: string, model
54
54
  const err = error as any;
55
55
  const status = readStatus(err);
56
56
  const url = readUrl(err);
57
- const detail = readProviderMessage(err);
57
+ let detail = readProviderMessage(err);
58
+ // Append OpenRouter-style raw metadata once (pi api/openai-completions.ts:719
59
+ // rule): normalize already stringifies error.error, so only append raw when
60
+ // absent to avoid double-printing.
61
+ const rawMeta =
62
+ (typeof err?.raw === "string" && err.raw.trim() ? err.raw.trim() : undefined) ??
63
+ (typeof err?.error?.metadata?.raw === "string" && err.error.metadata.raw.trim()
64
+ ? err.error.metadata.raw.trim()
65
+ : undefined);
66
+ if (rawMeta && !detail.includes(rawMeta.slice(0, 80))) {
67
+ detail = `${detail}\n${rawMeta.slice(0, 500)}`.slice(0, 800);
68
+ }
58
69
  const concise = new Error(
59
70
  `[${displayModel(providerId, modelId)}] request failed${status ? ` (${status})` : ""}: ${detail}`
60
71
  );
@@ -75,6 +86,9 @@ export function toConciseProviderError(error: unknown, providerId: string, model
75
86
  ...(typeof err?.errorType === "string"
76
87
  ? { errorType: { value: err.errorType, enumerable: false } }
77
88
  : {}),
89
+ ...(typeof (err as { raw?: unknown })?.raw === "string"
90
+ ? { raw: { value: (err as { raw: string }).raw.slice(0, 500), enumerable: false } }
91
+ : {}),
78
92
  });
79
93
  return concise;
80
94
  }
@@ -24,7 +24,7 @@ export function getPackageVersion(): string {
24
24
  const v = (globalThis as any).process?.env?.npm_package_version;
25
25
  if (typeof v === "string" && v) return v;
26
26
  } catch {}
27
- return "0.4.0";
27
+ return "0.4.5";
28
28
  }
29
29
 
30
30
  function getAgentAccelUserAgent(): string {