Merge branch 'codex/invariant-service-seam' into codex/invariant-package-registration-gate

This commit is contained in:
Tianyi Cui
2026-07-20 19:48:51 +08:00
391 changed files with 15415 additions and 8532 deletions

View File

@@ -23,7 +23,7 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i
## Mapping
`content``choices[0].message.content` (the generated answer). `sources[]` prefers the structured `search_results[]` (`url`, `title`, `snippet`, `publishedAt``date`), falling back to the URL-only `citations[]` array only when `search_results` is absent — those sources carry just a `url`, which is why `title`/`snippet`/`publishedAt` are optional on the seam. Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. Perplexity has no result-count control, so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`).
`content``choices[0].message.content` (the generated answer). `sources[]` prefers the structured `search_results[]` (`url`, `title`, `snippet`, `publishedAt``date`), falling back to the URL-only `citations[]` array only when `search_results` is absent — those sources carry just a `url`, which is why `title`/`snippet`/`publishedAt` are optional on the seam. Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. HTTP redirects are rejected before the `Location` target is contacted and surface as `WEB_PROVIDER_ERROR`. Perplexity has no result-count control, so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`).
## Model Experience

View File

@@ -82,7 +82,7 @@ export function mapPerplexityResponse(response: PerplexityResponse): WebSearchRe
}
}
/** The Perplexity-backed search provider. */
/** The Perplexity-backed search provider; HTTP redirects fail as `WEB_PROVIDER_ERROR`. */
export class PerplexitySearchProvider implements WebSearchProvider {
readonly id = PERPLEXITY_PROVIDER_ID
@@ -103,6 +103,7 @@ export class PerplexitySearchProvider implements WebSearchProvider {
try {
response = await fetch(`${this.options.baseURL}/chat/completions`, {
method: 'POST',
redirect: 'error',
headers: {
'authorization': `Bearer ${this.options.apiKey}`,
'content-type': 'application/json',

View File

@@ -90,6 +90,7 @@ describe('PerplexitySearchProvider request mapping', () => {
await new PerplexitySearchProvider(options).search({ query: 'hello' })
const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]
expect(url).toBe('https://api.perplexity.test/chat/completions')
expect(init).toMatchObject({ method: 'POST', redirect: 'error' })
expect((init.headers as Record<string, string>)['authorization']).toBe('Bearer pplx-key')
expect(JSON.parse(init.body as string)).toEqual({ model: 'sonar', max_tokens: 1024, messages: [{ role: 'user', content: 'hello' }] })
})