mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-22 16:47:34 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b6f53d61 |
@@ -112,12 +112,11 @@ jobs:
|
||||
- name: Run unit tests
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
# The runners have four vCPUs, and each Bun test process performs its own concurrent work.
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
GITHUB_ACTIONS=false bun turbo test --concurrency=3
|
||||
GITHUB_ACTIONS=false bun turbo test
|
||||
exit 0
|
||||
fi
|
||||
GITHUB_ACTIONS=false bun turbo test --affected --concurrency=3
|
||||
GITHUB_ACTIONS=false bun turbo test --affected
|
||||
env:
|
||||
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
|
||||
TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }}
|
||||
|
||||
@@ -29,47 +29,6 @@ await Effect.runPromise(program.pipe(Effect.provide(llmLayer)))
|
||||
|
||||
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
|
||||
|
||||
## Experimental evaluation
|
||||
|
||||
Evaluation models compare shared state with typed choice, score, and boolean questions. The API is
|
||||
isolated under an experimental entrypoint and provider namespace while the contract evolves:
|
||||
|
||||
```ts
|
||||
import { Effect } from "effect"
|
||||
import { Evaluation, EvaluationClient } from "@opencode/ai/experimental"
|
||||
import { TypeSafeAI } from "@opencode/ai/providers"
|
||||
|
||||
const model = TypeSafeAI.configure().experimental.evaluation("jev-latest")
|
||||
|
||||
const program = Evaluation.evaluate({
|
||||
model,
|
||||
state: "I was charged twice. Please refund the duplicate payment.",
|
||||
questions: {
|
||||
department: {
|
||||
type: "choice",
|
||||
instructions: "Which team should handle this?",
|
||||
criteria: { billing: "Payments and refunds", technical: "Bugs and outages" },
|
||||
},
|
||||
urgency: {
|
||||
type: "score",
|
||||
instructions: "How urgent is this?",
|
||||
criteria: ["Can wait", "Needs prompt attention", "Blocking revenue"],
|
||||
},
|
||||
refund: { type: "boolean", instructions: "Is the customer asking for a refund?" },
|
||||
},
|
||||
})
|
||||
|
||||
const response = await Effect.runPromise(program.pipe(Effect.provide(EvaluationClient.fetchLayer)))
|
||||
|
||||
console.log(response.answers.department.choice)
|
||||
console.log(response.answers.refund.probability)
|
||||
```
|
||||
|
||||
`TypeSafeAI` reads `TYPESAFE_API_KEY`. `OpenCodeZen` exposes the same selector and reads
|
||||
`OPENCODE_API_KEY`. The common API uses `boolean`; System One routes lower it to native `noul`.
|
||||
Choice and score confidence plus score legends remain available in provider metadata, and the
|
||||
provider's rounded probabilities are returned unchanged.
|
||||
|
||||
## Alibaba Cloud Model Studio
|
||||
|
||||
`Alibaba` provides standard Model Studio inference. Configure a region explicitly, then select
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
export { EvaluationClient } from "./experimental/evaluation-client.js"
|
||||
export {
|
||||
BooleanAnswer,
|
||||
BooleanQuestion,
|
||||
ChoiceAnswer,
|
||||
ChoiceQuestion,
|
||||
Evaluation,
|
||||
EvaluationAnswer,
|
||||
EvaluationInput,
|
||||
EvaluationModel,
|
||||
EvaluationModelSchema,
|
||||
EvaluationQuestion,
|
||||
EvaluationRequest,
|
||||
EvaluationResponse,
|
||||
EvaluationRounding,
|
||||
ScoreAnswer,
|
||||
ScoreQuestion,
|
||||
} from "./experimental/evaluation.js"
|
||||
export type {
|
||||
AnswerFor,
|
||||
AnswersFor,
|
||||
EvaluationModelOptions,
|
||||
EvaluationOptions,
|
||||
EvaluationQuestions,
|
||||
EvaluationRequestFor,
|
||||
EvaluationRequestInput,
|
||||
EvaluationResponseFor,
|
||||
EvaluationRoute,
|
||||
} from "./experimental/evaluation.js"
|
||||
@@ -1,54 +0,0 @@
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { RequestExecutor } from "../route/executor.js"
|
||||
import { mergeHttpOptions, type AIError } from "../schema/index.js"
|
||||
import { sanitizeSurrogates } from "../utils/sanitize.js"
|
||||
import {
|
||||
type EvaluationOptions,
|
||||
type EvaluationQuestions,
|
||||
type EvaluationRequestFor,
|
||||
type EvaluationResponseFor,
|
||||
} from "./evaluation.js"
|
||||
|
||||
export type Execute = RequestExecutor.Interface["execute"]
|
||||
|
||||
export interface Interface {
|
||||
readonly evaluate: <Options extends EvaluationOptions, const Questions extends EvaluationQuestions>(
|
||||
request: EvaluationRequestFor<Options, Questions>,
|
||||
) => Effect.Effect<EvaluationResponseFor<Questions>, AIError>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/AI/Experimental/EvaluationClient") {}
|
||||
|
||||
export const evaluate = <Options extends EvaluationOptions, const Questions extends EvaluationQuestions>(
|
||||
request: EvaluationRequestFor<Options, Questions>,
|
||||
): Effect.Effect<EvaluationResponseFor<Questions>, AIError, Service> =>
|
||||
Effect.flatMap(Service, (client) => client.evaluate(request))
|
||||
|
||||
export const layer: Layer.Layer<Service, never, RequestExecutor.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const executor = yield* RequestExecutor.Service
|
||||
return Service.of({
|
||||
evaluate: (request) =>
|
||||
request.model.route.evaluate(
|
||||
{
|
||||
...sanitizeSurrogates({
|
||||
...request,
|
||||
model: undefined,
|
||||
http: mergeHttpOptions(request.model.http, request.http),
|
||||
}),
|
||||
model: request.model,
|
||||
},
|
||||
executor.execute,
|
||||
),
|
||||
})
|
||||
}),
|
||||
)
|
||||
export const fetchLayer = layer.pipe(Layer.provide(RequestExecutor.fetchLayer))
|
||||
|
||||
export const EvaluationClient = {
|
||||
Service,
|
||||
layer,
|
||||
fetchLayer,
|
||||
evaluate,
|
||||
} as const
|
||||
@@ -1,245 +0,0 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import {
|
||||
AIError,
|
||||
HttpOptions,
|
||||
InvalidRequestError,
|
||||
ModelID,
|
||||
ProviderID,
|
||||
ProviderMetadata,
|
||||
Usage,
|
||||
} from "../schema/index.js"
|
||||
import { EvaluationClient, Service, type Execute } from "./evaluation-client.js"
|
||||
|
||||
export const EvaluationInput = Schema.Union([Schema.String, Schema.JsonObject, Schema.Array(Schema.Json)])
|
||||
export type EvaluationInput = Schema.Schema.Type<typeof EvaluationInput>
|
||||
|
||||
const EvaluationCriterion = Schema.NullOr(EvaluationInput)
|
||||
const ChoiceCriteria = Schema.Record(Schema.String, EvaluationCriterion).pipe(
|
||||
Schema.refine((x): x is typeof x => Object.keys(x).length > 0, {
|
||||
message: "Choice criteria must be a nonempty option map",
|
||||
}),
|
||||
)
|
||||
|
||||
export const ChoiceQuestion = Schema.Struct({
|
||||
type: Schema.Literal("choice"),
|
||||
instructions: EvaluationInput,
|
||||
criteria: ChoiceCriteria,
|
||||
})
|
||||
export type ChoiceQuestion = Schema.Schema.Type<typeof ChoiceQuestion>
|
||||
|
||||
export const ScoreQuestion = Schema.Struct({
|
||||
type: Schema.Literal("score"),
|
||||
instructions: EvaluationInput,
|
||||
criteria: Schema.Array(EvaluationCriterion).check(Schema.isMinLength(2)),
|
||||
})
|
||||
export type ScoreQuestion = Schema.Schema.Type<typeof ScoreQuestion>
|
||||
|
||||
export const BooleanQuestion = Schema.Struct({
|
||||
type: Schema.Literal("boolean"),
|
||||
instructions: EvaluationInput,
|
||||
criteria: Schema.optional(
|
||||
Schema.Struct({
|
||||
true: Schema.optional(EvaluationCriterion),
|
||||
false: Schema.optional(EvaluationCriterion),
|
||||
}),
|
||||
),
|
||||
})
|
||||
export type BooleanQuestion = Schema.Schema.Type<typeof BooleanQuestion>
|
||||
|
||||
export const EvaluationQuestion = Schema.Union([ChoiceQuestion, ScoreQuestion, BooleanQuestion]).pipe(
|
||||
Schema.toTaggedUnion("type"),
|
||||
)
|
||||
export type EvaluationQuestion = Schema.Schema.Type<typeof EvaluationQuestion>
|
||||
export type EvaluationQuestions = Readonly<Record<string, EvaluationQuestion>>
|
||||
const EvaluationQuestions = Schema.Record(Schema.String, EvaluationQuestion).pipe(
|
||||
Schema.refine((x): x is typeof x => Object.keys(x).length > 0, {
|
||||
message: "Evaluation questions must be a nonempty map",
|
||||
}),
|
||||
)
|
||||
|
||||
const Probability = Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))
|
||||
|
||||
export const ChoiceAnswer = Schema.Struct({
|
||||
type: Schema.Literal("choice"),
|
||||
choice: Schema.String,
|
||||
probabilities: Schema.optional(Schema.Record(Schema.String, Probability)),
|
||||
})
|
||||
export type ChoiceAnswer = Schema.Schema.Type<typeof ChoiceAnswer>
|
||||
|
||||
export const ScoreAnswer = Schema.Struct({
|
||||
type: Schema.Literal("score"),
|
||||
score: Schema.Number,
|
||||
probabilities: Schema.optional(Schema.Record(Schema.String, Probability)),
|
||||
})
|
||||
export type ScoreAnswer = Schema.Schema.Type<typeof ScoreAnswer>
|
||||
|
||||
export const BooleanAnswer = Schema.Struct({
|
||||
type: Schema.Literal("boolean"),
|
||||
probability: Probability,
|
||||
})
|
||||
export type BooleanAnswer = Schema.Schema.Type<typeof BooleanAnswer>
|
||||
|
||||
export const EvaluationAnswer = Schema.Union([ChoiceAnswer, ScoreAnswer, BooleanAnswer]).pipe(
|
||||
Schema.toTaggedUnion("type"),
|
||||
)
|
||||
export type EvaluationAnswer = Schema.Schema.Type<typeof EvaluationAnswer>
|
||||
|
||||
export type AnswerFor<Question extends EvaluationQuestion> = Question extends {
|
||||
readonly type: "choice"
|
||||
readonly criteria: infer Criteria
|
||||
}
|
||||
? {
|
||||
readonly type: "choice"
|
||||
readonly choice: Extract<keyof Criteria, string>
|
||||
readonly probabilities?: Readonly<Record<Extract<keyof Criteria, string>, number>>
|
||||
}
|
||||
: Question extends { readonly type: "score" }
|
||||
? ScoreAnswer
|
||||
: BooleanAnswer
|
||||
|
||||
export type AnswersFor<Questions extends EvaluationQuestions> = {
|
||||
readonly [ID in keyof Questions]: AnswerFor<Questions[ID]>
|
||||
}
|
||||
|
||||
export type EvaluationOptions = Record<string, unknown>
|
||||
|
||||
export interface EvaluationRoute<Options extends EvaluationOptions = EvaluationOptions> {
|
||||
readonly id: string
|
||||
readonly evaluate: <const Questions extends EvaluationQuestions>(
|
||||
request: EvaluationRequestFor<Options, Questions>,
|
||||
execute: Execute,
|
||||
) => Effect.Effect<EvaluationResponseFor<Questions>, AIError>
|
||||
}
|
||||
|
||||
export class EvaluationModel<Options extends EvaluationOptions = EvaluationOptions> {
|
||||
declare protected readonly _Options: (options: Options) => Options
|
||||
readonly id: ModelID
|
||||
readonly provider: ProviderID
|
||||
readonly route: EvaluationRoute<Options>
|
||||
readonly http?: HttpOptions
|
||||
|
||||
constructor(input: EvaluationModel.Input<Options>) {
|
||||
this.id = input.id
|
||||
this.provider = input.provider
|
||||
this.route = input.route
|
||||
this.http = input.http
|
||||
}
|
||||
|
||||
static make<Options extends EvaluationOptions = EvaluationOptions>(input: EvaluationModel.MakeInput<Options>) {
|
||||
return new EvaluationModel<Options>({
|
||||
id: ModelID.make(input.id),
|
||||
provider: ProviderID.make(input.provider),
|
||||
route: input.route,
|
||||
http: input.http,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export namespace EvaluationModel {
|
||||
export interface Input<Options extends EvaluationOptions = EvaluationOptions> {
|
||||
readonly id: ModelID
|
||||
readonly provider: ProviderID
|
||||
readonly route: EvaluationRoute<Options>
|
||||
readonly http?: HttpOptions
|
||||
}
|
||||
|
||||
export interface MakeInput<Options extends EvaluationOptions = EvaluationOptions>
|
||||
extends Omit<Input<Options>, "id" | "provider"> {
|
||||
readonly id: string | ModelID
|
||||
readonly provider: string | ProviderID
|
||||
}
|
||||
}
|
||||
|
||||
export const EvaluationModelSchema = Schema.declare(
|
||||
(value): value is EvaluationModel => value instanceof EvaluationModel,
|
||||
{
|
||||
expected: "Evaluation.Model",
|
||||
},
|
||||
)
|
||||
|
||||
export class EvaluationRequest extends Schema.Class<EvaluationRequest>("Evaluation.Request")({
|
||||
model: EvaluationModelSchema,
|
||||
state: EvaluationInput,
|
||||
questions: EvaluationQuestions,
|
||||
options: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
http: Schema.optional(HttpOptions),
|
||||
}) {
|
||||
declare protected readonly _EvaluationRequest: void
|
||||
}
|
||||
|
||||
export type EvaluationModelOptions<Model> = Model extends EvaluationModel<infer Options> ? Options : never
|
||||
|
||||
export type EvaluationRequestFor<
|
||||
Options extends EvaluationOptions = EvaluationOptions,
|
||||
Questions extends EvaluationQuestions = EvaluationQuestions,
|
||||
> = Omit<EvaluationRequest, "model" | "questions" | "options"> & {
|
||||
readonly model: EvaluationModel<Options>
|
||||
readonly questions: Questions
|
||||
readonly options?: Options
|
||||
}
|
||||
|
||||
export type EvaluationRequestInput<
|
||||
Model extends object = EvaluationModel,
|
||||
Questions extends EvaluationQuestions = EvaluationQuestions,
|
||||
> = Omit<ConstructorParameters<typeof EvaluationRequest>[0], "model" | "questions" | "options" | "http"> & {
|
||||
readonly model: Model
|
||||
readonly questions: Questions
|
||||
readonly options?: NoInfer<EvaluationModelOptions<Model>>
|
||||
readonly http?: HttpOptions.Input
|
||||
} & (Model extends EvaluationModel<EvaluationModelOptions<Model>> ? unknown : never)
|
||||
|
||||
export class EvaluationRounding extends Schema.Class<EvaluationRounding>("Evaluation.Rounding")({
|
||||
probabilityDecimals: Schema.optional(Schema.Int),
|
||||
scoreDecimals: Schema.optional(Schema.Int),
|
||||
}) {}
|
||||
|
||||
export class EvaluationResponse extends Schema.Class<EvaluationResponse>("Evaluation.Response")({
|
||||
model: ModelID,
|
||||
answers: Schema.Record(Schema.String, EvaluationAnswer),
|
||||
usage: Schema.optional(Usage),
|
||||
rounding: Schema.optional(EvaluationRounding),
|
||||
providerMetadata: Schema.optional(ProviderMetadata),
|
||||
}) {}
|
||||
|
||||
export type EvaluationResponseFor<Questions extends EvaluationQuestions> = Omit<EvaluationResponse, "answers"> & {
|
||||
readonly answers: AnswersFor<Questions>
|
||||
}
|
||||
|
||||
export function request<const Model extends object, const Questions extends EvaluationQuestions>(
|
||||
input: EvaluationRequestInput<Model, Questions>,
|
||||
): EvaluationRequestFor<EvaluationModelOptions<Model>, Questions>
|
||||
export function request(input: EvaluationRequest): EvaluationRequest
|
||||
export function request(input: EvaluationRequest | EvaluationRequestInput) {
|
||||
if (input instanceof EvaluationRequest) return input
|
||||
return new EvaluationRequest({
|
||||
...input,
|
||||
model: input.model as unknown as EvaluationModel,
|
||||
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
||||
})
|
||||
}
|
||||
|
||||
export function evaluate<const Model extends object, const Questions extends EvaluationQuestions>(
|
||||
input: EvaluationRequestInput<Model, Questions>,
|
||||
): Effect.Effect<EvaluationResponseFor<Questions>, AIError, Service>
|
||||
export function evaluate(input: EvaluationRequest): Effect.Effect<EvaluationResponse, AIError, Service>
|
||||
export function evaluate(input: EvaluationRequest | EvaluationRequestInput) {
|
||||
return Effect.try({
|
||||
try: () => (input instanceof EvaluationRequest ? input : request(input)),
|
||||
catch: (cause) =>
|
||||
new AIError({
|
||||
reason: new InvalidRequestError({
|
||||
message: cause instanceof Error ? cause.message : String(cause),
|
||||
cause,
|
||||
}),
|
||||
}),
|
||||
}).pipe(
|
||||
Effect.flatMap((request) =>
|
||||
EvaluationClient.evaluate(request as EvaluationRequestFor<EvaluationOptions, EvaluationQuestions>),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const Evaluation = {
|
||||
request,
|
||||
evaluate,
|
||||
} as const
|
||||
@@ -1,222 +0,0 @@
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Headers, HttpClientRequest } from "effect/unstable/http"
|
||||
import {
|
||||
BooleanAnswer,
|
||||
ChoiceAnswer,
|
||||
ChoiceQuestion,
|
||||
EvaluationInput,
|
||||
EvaluationModel,
|
||||
EvaluationResponse,
|
||||
EvaluationRounding,
|
||||
ScoreAnswer,
|
||||
ScoreQuestion,
|
||||
type EvaluationResponseFor,
|
||||
type EvaluationRoute,
|
||||
} from "./evaluation.js"
|
||||
import { Auth, type Definition as AuthDefinition } from "../route/auth.js"
|
||||
import {
|
||||
AIError,
|
||||
HttpContext,
|
||||
HttpOptions,
|
||||
InvalidProviderOutputError,
|
||||
InvalidRequestError,
|
||||
ModelID,
|
||||
Usage,
|
||||
mergeJsonRecords,
|
||||
} from "../schema/index.js"
|
||||
|
||||
const Noul = Schema.Struct({
|
||||
type: Schema.Literal("noul"),
|
||||
instructions: EvaluationInput,
|
||||
criteria: Schema.optional(
|
||||
Schema.Struct({
|
||||
true: Schema.optional(Schema.NullOr(EvaluationInput)),
|
||||
false: Schema.optional(Schema.NullOr(EvaluationInput)),
|
||||
}),
|
||||
),
|
||||
})
|
||||
const Question = Schema.Union([
|
||||
ChoiceQuestion.pipe(
|
||||
Schema.refine((x): x is typeof x => Object.keys(x.criteria).length <= 255, {
|
||||
message: "System One Choice questions support at most 255 options",
|
||||
}),
|
||||
),
|
||||
ScoreQuestion.pipe(
|
||||
Schema.refine((x): x is typeof x => x.criteria.length <= 10, {
|
||||
message: "System One Score questions support at most 10 levels",
|
||||
}),
|
||||
),
|
||||
Noul,
|
||||
])
|
||||
const Request = Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
model: Schema.String,
|
||||
state: EvaluationInput,
|
||||
questions: Schema.Record(Schema.String, Question),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
)
|
||||
|
||||
const Probability = Schema.Number.check(Schema.isBetween({ minimum: 0, maximum: 1 }))
|
||||
const NoulAnswer = Schema.Struct({ type: Schema.Literal("noul"), noul: Probability })
|
||||
const Choice = Schema.Struct({
|
||||
type: Schema.Literal("choice"),
|
||||
choice: Schema.String,
|
||||
probabilities: Schema.Record(Schema.String, Probability),
|
||||
confidence: Schema.optional(Probability),
|
||||
})
|
||||
const Score = Schema.Struct({
|
||||
type: Schema.Literal("score"),
|
||||
score: Schema.Number,
|
||||
probabilities: Schema.Record(Schema.String, Probability),
|
||||
legend: Schema.optional(Schema.Record(Schema.String, Schema.Json)),
|
||||
confidence: Schema.optional(Probability),
|
||||
})
|
||||
const NativeUsage = Schema.Struct({
|
||||
input_tokens: Schema.optional(Schema.Number),
|
||||
output_tokens: Schema.optional(Schema.Number),
|
||||
})
|
||||
|
||||
const encode = Schema.encodeUnknownEffect(Schema.fromJsonString(Request))
|
||||
const exact = (x: Readonly<Record<string, unknown>>, keys: ReadonlyArray<string>) =>
|
||||
Object.keys(x).length === keys.length && keys.every((key) => Object.hasOwn(x, key))
|
||||
|
||||
export interface ModelInput {
|
||||
readonly id: string | ModelID
|
||||
readonly provider: string
|
||||
readonly providerMetadataKey: string
|
||||
readonly auth: AuthDefinition
|
||||
readonly baseURL: string
|
||||
readonly headers?: Record<string, string>
|
||||
readonly http?: HttpOptions
|
||||
}
|
||||
|
||||
export const model = (cfg: ModelInput) => {
|
||||
const route: EvaluationRoute = {
|
||||
id: "system-one",
|
||||
evaluate: (req, send) =>
|
||||
Effect.gen(function* () {
|
||||
const url = new URL(`${cfg.baseURL.replace(/\/$/, "")}/systemone`)
|
||||
Object.entries(req.http?.query ?? {}).forEach(([key, value]) => url.searchParams.set(key, value))
|
||||
const body = yield* encode({
|
||||
...mergeJsonRecords(req.options, req.http?.body),
|
||||
model: req.model.id,
|
||||
state: req.state,
|
||||
questions: Object.fromEntries(
|
||||
Object.entries(req.questions).map(([id, x]) => [id, x.type === "boolean" ? { ...x, type: "noul" } : x]),
|
||||
),
|
||||
}).pipe(
|
||||
Effect.mapError(
|
||||
(cause) => new AIError({ reason: new InvalidRequestError({ message: cause.message, cause }) }),
|
||||
),
|
||||
)
|
||||
const headers = yield* Auth.toEffect(cfg.auth)({
|
||||
request: req,
|
||||
method: "POST",
|
||||
url: url.toString(),
|
||||
body,
|
||||
headers: Headers.fromInput({ ...cfg.headers, ...req.http?.headers }),
|
||||
})
|
||||
const res = yield* send(
|
||||
HttpClientRequest.post(url).pipe(
|
||||
HttpClientRequest.setHeaders(headers),
|
||||
HttpClientRequest.bodyText(body, "application/json"),
|
||||
),
|
||||
)
|
||||
const http = new HttpContext({ url: res.request.url, status: res.status, headers: res.headers })
|
||||
const fail = (message: string, cause: unknown, body?: string) =>
|
||||
new AIError({ reason: new InvalidProviderOutputError({ route: route.id, message, body, http, cause }) })
|
||||
const text = yield* res.text.pipe(
|
||||
Effect.mapError((cause) => fail("Failed to read the System One response", cause)),
|
||||
)
|
||||
const entries = Object.entries(req.questions)
|
||||
const output = Schema.Struct({
|
||||
model: Schema.String,
|
||||
answers: Schema.Struct(
|
||||
Object.fromEntries(
|
||||
entries.map(([id, question]) => {
|
||||
if (question.type === "boolean") return [id, NoulAnswer]
|
||||
if (question.type === "choice") {
|
||||
const keys = Object.keys(question.criteria)
|
||||
return [
|
||||
id,
|
||||
Choice.pipe(
|
||||
Schema.refine(
|
||||
(x): x is typeof x =>
|
||||
Object.hasOwn(question.criteria, x.choice) && exact(x.probabilities, keys),
|
||||
{ message: `Question "${id}" returned an invalid choice answer` },
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
const keys = question.criteria.map((_, index) => String(index))
|
||||
return [
|
||||
id,
|
||||
Score.pipe(
|
||||
Schema.refine(
|
||||
(x): x is typeof x =>
|
||||
x.score >= 0 && x.score <= question.criteria.length - 1 && exact(x.probabilities, keys),
|
||||
{ message: `Question "${id}" returned an invalid score answer` },
|
||||
),
|
||||
),
|
||||
]
|
||||
}),
|
||||
) as Record<string, Schema.Codec<unknown>>,
|
||||
),
|
||||
usage: Schema.optional(NativeUsage),
|
||||
})
|
||||
const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(output))(text).pipe(
|
||||
Effect.mapError((cause) => fail("System One returned an invalid response", cause, text)),
|
||||
)
|
||||
|
||||
const confidence: Record<string, number> = {}
|
||||
const legend: Record<string, Record<string, Schema.Json>> = {}
|
||||
const answers = Object.fromEntries(
|
||||
entries.map(([id, question]) => {
|
||||
const answer = data.answers[id]
|
||||
if (question.type === "boolean")
|
||||
return [
|
||||
id,
|
||||
{ type: "boolean", probability: (answer as typeof NoulAnswer.Type).noul } satisfies BooleanAnswer,
|
||||
]
|
||||
if (question.type === "choice") {
|
||||
const value = answer as typeof Choice.Type
|
||||
if (value.confidence !== undefined) confidence[id] = value.confidence
|
||||
return [
|
||||
id,
|
||||
{ type: "choice", choice: value.choice, probabilities: value.probabilities } satisfies ChoiceAnswer,
|
||||
]
|
||||
}
|
||||
const value = answer as typeof Score.Type
|
||||
if (value.confidence !== undefined) confidence[id] = value.confidence
|
||||
if (value.legend !== undefined) legend[id] = value.legend
|
||||
return [id, { type: "score", score: value.score, probabilities: value.probabilities } satisfies ScoreAnswer]
|
||||
}),
|
||||
)
|
||||
const meta = {
|
||||
...(Object.keys(confidence).length === 0 ? {} : { confidence }),
|
||||
...(Object.keys(legend).length === 0 ? {} : { legend }),
|
||||
}
|
||||
return new EvaluationResponse({
|
||||
model: ModelID.make(data.model),
|
||||
answers,
|
||||
usage: data.usage
|
||||
? new Usage({
|
||||
inputTokens: data.usage.input_tokens,
|
||||
outputTokens: data.usage.output_tokens,
|
||||
totalTokens:
|
||||
data.usage.input_tokens === undefined && data.usage.output_tokens === undefined
|
||||
? undefined
|
||||
: (data.usage.input_tokens ?? 0) + (data.usage.output_tokens ?? 0),
|
||||
providerMetadata: { [cfg.providerMetadataKey]: data.usage },
|
||||
})
|
||||
: undefined,
|
||||
rounding: new EvaluationRounding({ probabilityDecimals: 2, scoreDecimals: 2 }),
|
||||
providerMetadata: Object.keys(meta).length === 0 ? undefined : { [cfg.providerMetadataKey]: meta },
|
||||
}) as EvaluationResponseFor<typeof req.questions>
|
||||
}),
|
||||
}
|
||||
return EvaluationModel.make({ id: cfg.id, provider: cfg.provider, route, http: cfg.http })
|
||||
}
|
||||
|
||||
export const SystemOne = { model } as const
|
||||
@@ -38,13 +38,23 @@ export type Settings = ProviderPackage.Settings &
|
||||
|
||||
const fromRequest = Effect.fn("GoogleVertex.fromRequest")(function* (request: LLMRequest) {
|
||||
const { serviceTier: _, ...body } = yield* Gemini.protocol.body.from(request)
|
||||
// Vertex's native REST schema rejects `id` on FunctionCall/FunctionResponse parts with HTTP 400,
|
||||
// unlike AI Studio, so history minted there cannot be lowered verbatim.
|
||||
const contents = body.contents.map((content) => ({
|
||||
...content,
|
||||
parts: (content.parts ?? []).map((part) => {
|
||||
if ("functionCall" in part) return { ...part, functionCall: { ...part.functionCall, id: undefined } }
|
||||
if ("functionResponse" in part) return { ...part, functionResponse: { ...part.functionResponse, id: undefined } }
|
||||
return part
|
||||
}),
|
||||
}))
|
||||
const value = request.providerOptions?.labels
|
||||
const labels = ProviderShared.isRecord(value)
|
||||
? Object.fromEntries(
|
||||
Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === "string"),
|
||||
)
|
||||
: undefined
|
||||
return { ...body, labels }
|
||||
return { ...body, contents, labels }
|
||||
})
|
||||
|
||||
const protocol = {
|
||||
|
||||
@@ -24,10 +24,8 @@ export * as Moonshot from "./moonshot.js"
|
||||
export * as OpenAI from "./openai.js"
|
||||
export * as OpenAICompatible from "./openai-compatible.js"
|
||||
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
|
||||
export * as OpenCodeZen from "./opencode-zen.js"
|
||||
export * as OpenRouter from "./openrouter.js"
|
||||
export * as TogetherAI from "./togetherai.js"
|
||||
export * as TypeSafeAI from "./typesafe-ai.js"
|
||||
export * as XAI from "./xai.js"
|
||||
export * as ZAI from "./zai.js"
|
||||
export * as ZAICodingPlan from "./zai-coding-plan.js"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { HttpOptions, ProviderID, type ModelID } from "../schema/index.js"
|
||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
||||
import { SystemOne } from "../experimental/system-one.js"
|
||||
|
||||
export const id = ProviderID.make("opencode")
|
||||
const baseURL = "https://opencode.ai/zen/v1"
|
||||
|
||||
export type Options = ProviderAuthOption<"optional"> & {
|
||||
readonly baseURL?: string
|
||||
readonly headers?: Record<string, string>
|
||||
readonly http?: HttpOptions.Input
|
||||
}
|
||||
|
||||
export const configure = (input: Options = {}) => {
|
||||
const evaluation = (modelID: string | ModelID) =>
|
||||
SystemOne.model({
|
||||
id: modelID,
|
||||
provider: id,
|
||||
providerMetadataKey: "opencode",
|
||||
auth: AuthOptions.bearer(input, "OPENCODE_API_KEY"),
|
||||
baseURL: input.baseURL ?? baseURL,
|
||||
headers: input.headers,
|
||||
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
||||
})
|
||||
return { id, experimental: { evaluation }, configure }
|
||||
}
|
||||
|
||||
export const provider = configure()
|
||||
export const experimental = provider.experimental
|
||||
|
||||
export * as OpenCodeZen from "./opencode-zen.js"
|
||||
@@ -1,31 +0,0 @@
|
||||
import { HttpOptions, ProviderID, type ModelID } from "../schema/index.js"
|
||||
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
|
||||
import { SystemOne } from "../experimental/system-one.js"
|
||||
|
||||
export const id = ProviderID.make("typesafe-ai")
|
||||
const baseURL = "https://api.typesafe.ai/v1"
|
||||
|
||||
export type Options = ProviderAuthOption<"optional"> & {
|
||||
readonly baseURL?: string
|
||||
readonly headers?: Record<string, string>
|
||||
readonly http?: HttpOptions.Input
|
||||
}
|
||||
|
||||
export const configure = (input: Options = {}) => {
|
||||
const evaluation = (modelID: string | ModelID) =>
|
||||
SystemOne.model({
|
||||
id: modelID,
|
||||
provider: id,
|
||||
providerMetadataKey: "typesafe",
|
||||
auth: AuthOptions.bearer(input, "TYPESAFE_API_KEY"),
|
||||
baseURL: input.baseURL ?? baseURL,
|
||||
headers: input.headers,
|
||||
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
|
||||
})
|
||||
return { id, experimental: { evaluation }, configure }
|
||||
}
|
||||
|
||||
export const provider = configure()
|
||||
export const experimental = provider.experimental
|
||||
|
||||
export * as TypeSafeAI from "./typesafe-ai.js"
|
||||
@@ -1,173 +0,0 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpClientRequest } from "effect/unstable/http"
|
||||
import { Evaluation, EvaluationClient } from "../src/experimental.js"
|
||||
import { OpenCodeZen, TypeSafeAI } from "../src/providers.js"
|
||||
import { it } from "./lib/effect.js"
|
||||
import { dynamicResponse } from "./lib/http.js"
|
||||
|
||||
describe("experimental Evaluation", () => {
|
||||
it.effect("evaluates typed questions through System One", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* Evaluation.evaluate({
|
||||
model: TypeSafeAI.configure({
|
||||
apiKey: "test",
|
||||
baseURL: "https://typesafe.test/v1/",
|
||||
headers: { "x-default": "yes" },
|
||||
http: { body: { deployment: "test" }, query: { api: "v1" } },
|
||||
}).experimental.evaluation("jev-latest"),
|
||||
state: { ticket: "Please refund the duplicate charge." },
|
||||
questions: {
|
||||
department: {
|
||||
type: "choice",
|
||||
instructions: "Which team should handle this?",
|
||||
criteria: { billing: "Payments and refunds", technical: "Bugs and outages" },
|
||||
},
|
||||
urgency: {
|
||||
type: "score",
|
||||
instructions: "How urgent is this?",
|
||||
criteria: ["Can wait", "Needs attention", "Blocking"],
|
||||
},
|
||||
refund: { type: "boolean", instructions: "Is the customer asking for a refund?" },
|
||||
},
|
||||
options: { trace: { enabled: true } },
|
||||
http: { body: { request_metadata: "value" }, headers: { "x-request": "yes" }, query: { trace: "1" } },
|
||||
})
|
||||
|
||||
expect(response.model).toBe("jev-1.13.0")
|
||||
expect(response.answers.department).toEqual({
|
||||
type: "choice",
|
||||
choice: "billing",
|
||||
probabilities: { billing: 0.9, technical: 0.1 },
|
||||
})
|
||||
expect(response.answers.urgency).toEqual({
|
||||
type: "score",
|
||||
score: 1.2,
|
||||
probabilities: { "0": 0, "1": 0.8, "2": 0.2 },
|
||||
})
|
||||
expect(response.answers.refund).toEqual({ type: "boolean", probability: 0.97 })
|
||||
expect(response.usage?.totalTokens).toBe(36)
|
||||
expect(response.providerMetadata).toEqual({
|
||||
typesafe: {
|
||||
confidence: { department: 0.8, urgency: 0.6 },
|
||||
legend: { urgency: { "0": "Can wait", "1": "Needs attention", "2": "Blocking" } },
|
||||
},
|
||||
})
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
EvaluationClient.layer.pipe(
|
||||
Layer.provide(
|
||||
dynamicResponse((input) =>
|
||||
Effect.gen(function* () {
|
||||
const request = yield* HttpClientRequest.toWeb(input.request).pipe(Effect.orDie)
|
||||
expect(request.url).toBe("https://typesafe.test/v1/systemone?api=v1&trace=1")
|
||||
expect(request.headers.get("authorization")).toBe("Bearer test")
|
||||
expect(request.headers.get("x-default")).toBe("yes")
|
||||
expect(request.headers.get("x-request")).toBe("yes")
|
||||
expect(JSON.parse(input.text)).toEqual({
|
||||
deployment: "test",
|
||||
request_metadata: "value",
|
||||
trace: { enabled: true },
|
||||
model: "jev-latest",
|
||||
state: { ticket: "Please refund the duplicate charge." },
|
||||
questions: {
|
||||
department: {
|
||||
type: "choice",
|
||||
instructions: "Which team should handle this?",
|
||||
criteria: { billing: "Payments and refunds", technical: "Bugs and outages" },
|
||||
},
|
||||
urgency: {
|
||||
type: "score",
|
||||
instructions: "How urgent is this?",
|
||||
criteria: ["Can wait", "Needs attention", "Blocking"],
|
||||
},
|
||||
refund: { type: "noul", instructions: "Is the customer asking for a refund?" },
|
||||
},
|
||||
})
|
||||
return input.respond(
|
||||
JSON.stringify({
|
||||
model: "jev-1.13.0",
|
||||
answers: {
|
||||
department: {
|
||||
type: "choice",
|
||||
choice: "billing",
|
||||
probabilities: { billing: 0.9, technical: 0.1 },
|
||||
confidence: 0.8,
|
||||
},
|
||||
urgency: {
|
||||
type: "score",
|
||||
score: 1.2,
|
||||
probabilities: { "0": 0, "1": 0.8, "2": 0.2 },
|
||||
legend: { "0": "Can wait", "1": "Needs attention", "2": "Blocking" },
|
||||
confidence: 0.6,
|
||||
},
|
||||
refund: { type: "noul", noul: 0.97 },
|
||||
},
|
||||
usage: { input_tokens: 30, output_tokens: 6 },
|
||||
}),
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("configures the OpenCode Zen System One endpoint", () =>
|
||||
Evaluation.evaluate({
|
||||
model: OpenCodeZen.configure({ apiKey: "zen-key", baseURL: "https://zen.test/v1" }).experimental.evaluation(
|
||||
"jev-1.13",
|
||||
),
|
||||
state: "hello",
|
||||
questions: { greeting: { type: "boolean", instructions: "Is this a greeting?" } },
|
||||
}).pipe(
|
||||
Effect.tap((response) =>
|
||||
Effect.sync(() => {
|
||||
expect(response.answers.greeting.probability).toBe(0.99)
|
||||
expect(response.usage?.providerMetadata).toEqual({
|
||||
opencode: { input_tokens: 10, output_tokens: 2 },
|
||||
})
|
||||
}),
|
||||
),
|
||||
Effect.provide(
|
||||
EvaluationClient.layer.pipe(
|
||||
Layer.provide(
|
||||
dynamicResponse((input) => {
|
||||
expect(input.request.url).toBe("https://zen.test/v1/systemone")
|
||||
expect(input.request.headers.authorization).toBe("Bearer zen-key")
|
||||
return Effect.succeed(
|
||||
input.respond(
|
||||
JSON.stringify({
|
||||
model: "jev-1.13.0",
|
||||
answers: { greeting: { type: "noul", noul: 0.99 } },
|
||||
usage: { input_tokens: 10, output_tokens: 2 },
|
||||
}),
|
||||
{ headers: { "content-type": "application/json" } },
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("rejects malformed questions before network I/O", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* Evaluation.evaluate({
|
||||
model: TypeSafeAI.experimental.evaluation("jev-latest"),
|
||||
state: "hello",
|
||||
questions: { score: { type: "score", instructions: "How much?", criteria: ["only"] } },
|
||||
}).pipe(Effect.flip)
|
||||
expect(error.reason._tag).toBe("InvalidRequest")
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
EvaluationClient.layer.pipe(
|
||||
Layer.provide(dynamicResponse(() => Effect.die("invalid evaluation reached the network"))),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Effect } from "effect"
|
||||
import { Evaluation, EvaluationClient, EvaluationModel, type EvaluationRoute } from "../src/experimental.js"
|
||||
import type { Service } from "../src/experimental/evaluation-client.js"
|
||||
import { OpenCodeZen, TypeSafeAI } from "../src/providers.js"
|
||||
|
||||
type Requirements<T> = T extends Effect.Effect<infer _A, infer _E, infer R> ? R : never
|
||||
type Success<T> = T extends Effect.Effect<infer A, infer _E, infer _R> ? A : never
|
||||
type Equal<A, B> = [A, B] extends [B, A] ? true : false
|
||||
type Assert<T extends true> = T
|
||||
|
||||
const model = TypeSafeAI.configure({ apiKey: "test" }).experimental.evaluation("jev-latest")
|
||||
const request = Evaluation.request({
|
||||
model,
|
||||
state: { ticket: "refund" },
|
||||
questions: {
|
||||
topic: {
|
||||
type: "choice",
|
||||
instructions: "Which team?",
|
||||
criteria: { billing: null, support: { includes: ["help"] } },
|
||||
},
|
||||
severity: { type: "score", instructions: "How severe?", criteria: ["Low", "High"] },
|
||||
refund: { type: "boolean", instructions: "Refund?" },
|
||||
},
|
||||
})
|
||||
|
||||
const result = EvaluationClient.evaluate(request)
|
||||
type Result = Success<typeof result>
|
||||
type Choice = Assert<Equal<Result["answers"]["topic"]["choice"], "billing" | "support">>
|
||||
type ClientRequirements = Assert<Equal<Requirements<typeof result>, Service>>
|
||||
void (true satisfies Choice)
|
||||
void (true satisfies ClientRequirements)
|
||||
|
||||
Effect.gen(function* () {
|
||||
const response = yield* Evaluation.evaluate({
|
||||
model: OpenCodeZen.experimental.evaluation("jev-1.13"),
|
||||
state: ["hello"],
|
||||
questions: { greeting: { type: "boolean", instructions: "Greeting?" } },
|
||||
})
|
||||
response.answers.greeting.probability satisfies number
|
||||
// @ts-expect-error Boolean answers do not contain a selected choice.
|
||||
response.answers.greeting.choice
|
||||
// @ts-expect-error Unknown question IDs are not exposed.
|
||||
response.answers.missing
|
||||
})
|
||||
|
||||
declare const route: EvaluationRoute<{ readonly temperature?: number }>
|
||||
const custom = EvaluationModel.make({ id: "custom", provider: "custom", route })
|
||||
Evaluation.evaluate({
|
||||
model: custom,
|
||||
state: "hello",
|
||||
questions: { ok: { type: "boolean", instructions: "OK?" } },
|
||||
options: { temperature: 0.5 },
|
||||
})
|
||||
// @ts-expect-error Selected evaluation models retain their request option types.
|
||||
Evaluation.evaluate({
|
||||
model: custom,
|
||||
state: "hello",
|
||||
questions: { ok: { type: "boolean", instructions: "OK?" } },
|
||||
options: { temperature: "high" },
|
||||
})
|
||||
@@ -8,11 +8,9 @@ import {
|
||||
CloudflareWorkersAI,
|
||||
DeepSeek,
|
||||
Fireworks,
|
||||
OpenCodeZen,
|
||||
OpenAI,
|
||||
OpenAICompatible,
|
||||
OpenRouter,
|
||||
TypeSafeAI,
|
||||
XAI,
|
||||
} from "@opencode/ai/providers"
|
||||
import {
|
||||
@@ -25,7 +23,6 @@ import {
|
||||
} from "@opencode/ai/protocols"
|
||||
import * as AnthropicMessages from "@opencode/ai/protocols/anthropic-messages"
|
||||
import { TestLLM } from "@opencode/ai/testing"
|
||||
import { Evaluation, EvaluationClient } from "@opencode/ai/experimental"
|
||||
|
||||
describe("public exports", () => {
|
||||
test("root exposes app-facing runtime APIs", () => {
|
||||
@@ -40,9 +37,6 @@ describe("public exports", () => {
|
||||
expect(TestLLM.layer).toBeFunction()
|
||||
expect(TestLLM.testLayer).toBeFunction()
|
||||
expect(TestLLM.Test.of).toBeFunction()
|
||||
expect(Evaluation.evaluate).toBeFunction()
|
||||
expect(EvaluationClient.layer).toBeDefined()
|
||||
expect(EvaluationClient.fetchLayer).toBeDefined()
|
||||
})
|
||||
|
||||
test("route barrel exposes route-authoring APIs", () => {
|
||||
@@ -72,8 +66,6 @@ describe("public exports", () => {
|
||||
expect(CloudflareWorkersAI.configure).toBeFunction()
|
||||
expect(CloudflareWorkersAI.configure({ accountId: "fixture", apiKey: "fixture" }).model).toBeFunction()
|
||||
expect(OpenRouter.model).toBeFunction()
|
||||
expect(TypeSafeAI.experimental.evaluation).toBeFunction()
|
||||
expect(OpenCodeZen.experimental.evaluation).toBeFunction()
|
||||
expect(XAI.model).toBeFunction()
|
||||
expect(XAI.provider.responses).toBe(XAI.responses)
|
||||
expect(XAI.provider.chat).toBe(XAI.chat)
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:google-vertex",
|
||||
"provider:google-vertex",
|
||||
"protocol:gemini"
|
||||
],
|
||||
"tags": ["prefix:google-vertex", "provider:google-vertex", "protocol:gemini"],
|
||||
"name": "google-vertex/calls-a-tool",
|
||||
"recordedAt": "2026-09-22T03:46:29.725Z"
|
||||
"recordedAt": "2026-08-23T17:21:51.036Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -25,7 +21,7 @@
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"functionCall\": {\"name\": \"lookup_weather\",\"args\": {\"city\": \"Paris\"},\"id\": \"call_28936\"},\"thoughtSignature\": \"AY89a1/MVeBxtwu9l/96clFobOrrd7Q5MTf5o8/A22fc++1EsFYVeCFx9WCGmJ+D3yW3FWlRXKtJECYfprSoxixCSOYBNiV9g8/IkejbAxF26k1vxTsHkoqts0O2s8GqqBSRAavJfKQ/taTRbPuIq+b+RKZ8SDJFoWDnFjXpdS5S164uJTjmgs4ALd8oB1oQRycMsTbAY51TmkcNUnhARpjKQfqiizz4KfSRFjx7xDnza0v4jj73m2GV86eLDhVT7trIT+Dl5DsJt6RBLOE2Amse54mcpIbsYaANlY1YDU6FKuUBqnuwpl0OfhMXwWuYMogv1tH/0rTXsqNwmHyt/b9ZZXqYCdFXpMGt8udmoZ0d\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:28.953809Z\",\"responseId\": \"FPqxatGbOsSorb8PtY2z8Q4\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 39,\"candidatesTokenCount\": 16,\"totalTokenCount\": 93,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 39}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 16}],\"thoughtsTokenCount\": 38},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:28.953809Z\",\"responseId\": \"FPqxatGbOsSorb8PtY2z8Q4\"}\r\n\r\n"
|
||||
"body": "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"functionCall\": {\"name\": \"lookup_weather\",\"args\": {\"city\": \"Paris\"},\"id\": \"call_425130\"},\"thoughtSignature\": \"AY89a1+1fXnLgYhHMuN3Ak6LBhT6PcrYOW7iPav4LfsacvG/Z6l1yJ+AsU7vWhFj/JyPIbsJJQ+GjohM9sCIZ6nqUOIg3reo/7osmrCvFrVHedTHQcwiPzoz2Kp3gb+uWjFAXxk1EX4IRAKcu0ox1W/Z9PpuZvHkTerGO2a82e02N6MAF1YhhtbXFvSdqLRih2Os68rdOk5/Bcld7ol8qUgeyIZ3CtI3OJ5jwRcD8LjvK33A7ZFzH5Bxp/peUmXvqnu5iNhnGBxZaJy/vupCtxRZxjaS+ojG0/UhyrnRiKIpbzQ0FBkxePPn8GCX/LOe2y3GUc98co8lN8OOuCd9ZmEdx5AjHmQkPO9fAV9SxG6Bda6SDWVL8o/Uz3WSQYoUEfAdoajEWIBvcisoeCJjb7zgmRRZ9VQSPl3RXj5LFRvX8jn0YKV1CahYbc24jA==\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-08-23T17:21:50.308576Z\",\"responseId\": \"LiyLauDqErCErb8Pj8aWkAs\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 39,\"candidatesTokenCount\": 16,\"totalTokenCount\": 102,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 39}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 16}],\"thoughtsTokenCount\": 47},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-08-23T17:21:50.308576Z\",\"responseId\": \"LiyLauDqErCErb8Pj8aWkAs\"}\r\n\r\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+4
-8
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:google-vertex",
|
||||
"provider:google-vertex",
|
||||
"protocol:gemini"
|
||||
],
|
||||
"tags": ["prefix:google-vertex", "provider:google-vertex", "protocol:gemini"],
|
||||
"name": "google-vertex/continues-after-a-tool-result",
|
||||
"recordedAt": "2026-09-22T03:46:30.691Z"
|
||||
"recordedAt": "2026-08-23T17:21:51.853Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -18,14 +14,14 @@
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"contents\":[{\"role\":\"user\",\"parts\":[{\"text\":\"What is the weather in Paris?\"}]},{\"role\":\"model\",\"parts\":[{\"functionCall\":{\"id\":\"call_paris_1\",\"name\":\"lookup_weather\",\"args\":{\"city\":\"Paris\"}},\"thoughtSignature\":\"skip_thought_signature_validator\"}]},{\"role\":\"user\",\"parts\":[{\"functionResponse\":{\"id\":\"call_paris_1\",\"name\":\"lookup_weather\",\"response\":{\"name\":\"lookup_weather\",\"content\":\"18C, light rain\"}}}]}],\"tools\":[{\"functionDeclarations\":[{\"name\":\"lookup_weather\",\"description\":\"Look up the current weather for a city\",\"parameters\":{\"required\":[\"city\"],\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}}}}]}]}"
|
||||
"body": "{\"contents\":[{\"role\":\"user\",\"parts\":[{\"text\":\"What is the weather in Paris?\"}]},{\"role\":\"model\",\"parts\":[{\"functionCall\":{\"name\":\"lookup_weather\",\"args\":{\"city\":\"Paris\"}},\"thoughtSignature\":\"skip_thought_signature_validator\"}]},{\"role\":\"user\",\"parts\":[{\"functionResponse\":{\"name\":\"lookup_weather\",\"response\":{\"name\":\"lookup_weather\",\"content\":\"18C, light rain\"}}}]}],\"tools\":[{\"functionDeclarations\":[{\"name\":\"lookup_weather\",\"description\":\"Look up the current weather for a city\",\"parameters\":{\"required\":[\"city\"],\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}}}}]}]}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"The weather\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:29.830958Z\",\"responseId\": \"Ffqxau7bMoCOrb8P_Iu5sA0\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \" in Paris is currently 18°C with light rain.\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:29.830958Z\",\"responseId\": \"Ffqxau7bMoCOrb8P_Iu5sA0\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\",\"thoughtSignature\": \"AY89a18aVI8Uxr2VaOI822u0DyuQdy4B00uhLnjqYb5Qb6Mkscccm018knLtYThB5UX8dRv1VFsORSQ0Qo6Gx9RCng2AK9EPce7p\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 59,\"candidatesTokenCount\": 15,\"totalTokenCount\": 74,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 59}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 15}]},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:29.830958Z\",\"responseId\": \"Ffqxau7bMoCOrb8P_Iu5sA0\"}\r\n\r\n"
|
||||
"body": "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"The weather in Paris is currently 18°C with light rain.\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-08-23T17:21:51.220919Z\",\"responseId\": \"LyyLave9DbWnrb8P1IjLmQQ\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\",\"thoughtSignature\": \"AY89a197c+fpHJftPtcufnqMAyoRQKVEQK+KeG+RVHVx2wKil3L4jP4YWvfVbcuOFr2jio4Kre/hCrDANAoMFSvaZrdaPeo1b5bXQSmJKMH03yM5M6q6ME6JiBvXym143U4exIde4UbOh2tMeyXMvB3aWxcavIHd78g5G5QPLreo6A3LO5871cYYVeRwteY+/zbEdqfaAq1hlk6WYpWkNljYpjMyKwr15YC8rFLh3HYayS9tTN++GGrk/reZn6C3OEPlzPou/pXRATzcEAGVl/TW\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 59,\"candidatesTokenCount\": 15,\"totalTokenCount\": 98,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 59}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 15}],\"thoughtsTokenCount\": 24},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-08-23T17:21:51.220919Z\",\"responseId\": \"LyyLave9DbWnrb8P1IjLmQQ\"}\r\n\r\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"prefix:google-vertex",
|
||||
"provider:google-vertex",
|
||||
"protocol:gemini"
|
||||
],
|
||||
"tags": ["prefix:google-vertex", "provider:google-vertex", "protocol:gemini"],
|
||||
"name": "google-vertex/streams-text",
|
||||
"recordedAt": "2026-09-22T03:46:28.840Z"
|
||||
"recordedAt": "2026-08-23T17:21:50.112Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -25,7 +21,7 @@
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"Hello\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:26.748531Z\",\"responseId\": \"EvqxavPXLbqerb8P1eSViQs\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\",\"thoughtSignature\": \"AY89a18msz0aRAZc4U/Zl7rEXjdTJsE0EWee1+hD2dT7h711q6uIRem7KWCLlji7JVSu2hDV1L8uFh8SGAO5jU4MBSZ+mq661EVXf9DZBF1CV+fetSpCUggurbYHWGGGvLRxjI96W0wR93EMoV/1pLHa4fgGVcFupoFsz/3n9J3wCkT4pRvHjn+hC7V11aSMI07t/OUeo1ssAziob9apuB+HUm2/EO17ph9TZvg3rydE5J0nmJ+DnYGLl5FfQJHgnnsjR5qaKs5wVNIKNA811lv/K5Iee/O6gMNXI3GAcGUvPKHMD5oQSs5B343blFUiqw47H1WZ9KWBoLXhVcnss6hLPmpDchInVhr3ObAHQhZIaBDBnhQR6nuc5BtgoFnwWw3w5G1yNNFt3qT3c75Rd/8NamdtveyskDbrHxQ5GkUo7ICuE/3SQG0yqWhVDjakYgVqjLG3BTKRg6I/dT5+VCeQbdxTZ+ScWkJfXYDEnlZlTjMLz2FS+B+9Zwez/1Dy/pm9H51jvuXI942kv31DfDVcTTwvKmb3SqIB9D4aomb16gDAywC7I4L/+ZwsqmcQoUvVtHGEe5NL3r6fgWtWzKht+60ZgBalmzdBfhZHd/bDXvTAX7SVpHraXjfpcdGrtv1LhU6Kr3gol94TR5Xh9dWKNhwR0s6at7MNCpj3eG9ogrWkmYSiqNeVT1ovscWvH70tDCtCNwY5sIUJVUSWdHc2OdzFxNIPkzh9OuHIn0o9Dbmup6obFoQOgi587HA/Sqvre9pDVqVMzn7w57naDmg+lsR9wS67BxbV4tVrvvfeVQAQQRft5SmSu7WCBnaWuM2/o890nE5ynFG+M2FqApJLgdfXozOguo/hROrD3WxLUyT8+FDsoCY8ky6YYTaF3OlOHpuD5Zmw4UGSdDbcEzhc6cRqBAYfMpxzN58VwNRrjG/MGTY3jrvOLJDk3sF7URa7Tn5YuRLp/2jL5YKWF2QftWiW9jl1YeORYQwNdLBuRK9L4/WOxNRWhYZToAJfwT4byh5dKzY8OnjeFcJy8RVvuRpoYeQY1P63ie/K1D3TgJEwKQXylLdPwtZRGM/P5sm2dcf5F+lRfZGufmv+Q++unrqWr6uv3IlgVGKPa8zTh+ipSaik7taKd+jertOSYkgjrrUfjC0/oyg5giuNqXh+ebIxQC7TMX8bManc5N3aomFuYXmIJ4iT/euQctq8N3gPPWmUNmrnXztdEZgpQdV2ahj+yRr1yxCriOfRo3Az3oPMPEaXxrqDc8RoWt/lOgzzp7KfBGoOVzYdvaDGr8MhsZ8Pd7HD0vZW8TNSl8R7cEK3G5EotNA+s6lF/n/RH5ewmg3cQK8bnnZ4oNjqYo3pGtw+H2rbefMMzAk4mybGe9uDqSdhWJgBzRGsMXRW+0atn9tuJTu6vPNn9asNvwXS0lcvpQskbe5sK3yhzyFzqwrJxs7Ji6yiu6kOtsQ5Girzk1835A2Zun6ZegGtQH8DbgIPdSBMkh6kypndBu4ns2gg97g9yB8XFGbv3c60sxaXPRuycl9IjBL81Yy6n6PCOBx7Pqm9xrzPn7QzgtetKxCmNUMYJvF+myiQFrMdL1w+1DD7FoZX9VNBDbNWgcfhgnL8tlHSmTb36KnScJOe8D+3n3+aDfkWpiHjrLrGJsHlaek/Ji2Cgwa0dIC6EhQitNttskIKVMa+yD6D1T6Dvz/eolnutjYwbGb3y9mtpQzmVwf2Zp2dzxYRWZk=\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 7,\"candidatesTokenCount\": 1,\"totalTokenCount\": 285,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 1}],\"thoughtsTokenCount\": 277},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-09-22T03:46:26.748531Z\",\"responseId\": \"EvqxavPXLbqerb8P1eSViQs\"}\r\n\r\n"
|
||||
"body": "data: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"Hello\"}]}}],\"usageMetadata\": {\"trafficType\": \"ON_DEMAND\"},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-08-23T17:21:48.528714Z\",\"responseId\": \"LCyLasqiIO6crb8P1sDboQc\"}\r\n\r\ndata: {\"candidates\": [{\"content\": {\"role\": \"model\",\"parts\": [{\"text\": \"\",\"thoughtSignature\": \"AY89a1+BGsRqlGpfT0psLB4jeTkT5rDV2HFOlrRuF7aVxDOjqNVUku6t4azeSnxpd+msHWuwXj4RS+7gmVlzVs+JNi8uj+iZWTBCi71vSh9kdK9ed/sHv9J7uL9ZWSOcgbhX/hxdXaUp5yVbQzHFXPjR9A/IkEkHV8VKarDZVFE1T1uASia74lkmyBeZZz+DQmRsLwbUHzFUKlF3qnk/SliLo21ZgASd7itlALQ0PBLJZwgeI3g7tDscDSE18hnB11Fky8q7MLd3HY16zbDvHBEMb18pmmPelPI01KdrCIwMSou/01/u5jiSUCc3pFksZawUj3tAHocHSC3ZKAQQQuUXGe5tm61C2E40/NANBeePc1S4HYE6Yo/vtX6tE02LDky5IQWX09H6+DZ7fpopP5nCUfcKPHa3hVjYquWYYMtZgXO4ZpxfVd3lt1VUDuJNN3BMMCZapjBoJZFPXPJ5t/yg9Rnd791+msGH77b4wztz1vtsPrT9oV9g6SDo9ZUH6BaOcbK7fw8FaXcGw+55malEwQy6zpRLGecooBu70p6RwhaAUyKIMX49y+F2hkNxQxDeBUNckJnu6n4w+KLyjP+bR0gqPJbGjVfteHm+QujqjJdBBT/m1u9kPo1nIbzdEs/PIADBdbuV7TkD/HoRFKpLnNmM2no8ioTtFEjKBDz4ippGi15r8pGgA6wIb/1HAvOGh+PVERdGcbelVTgfONwBqjQ7B1wmEizCfyYuMIskfwjxDGayfKlpDxrnNeogtEct9u5/DjEKlURlg9MtmW1B9P8BXYJ+7SCiRJWwW6bzB+5C+MLCnETl/mljDizoJMHK8DKIhI4oxBsrWXEuoHFwEwGIeOZq0BofH2Jz/l6+KIboV/zd581Kk0zPg/rlI6acfjUEtXtbF+t0+jzoJN7006x4i2tqXeJZ+4e5yisSArEsfJ0YzNWoJtBHG9V9/euDcEP3+jsr98efaQaQbLMPvT/Hb7CYQ7ChhGfcGxQ=\"}]},\"finishReason\": \"STOP\"}],\"usageMetadata\": {\"promptTokenCount\": 7,\"candidatesTokenCount\": 1,\"totalTokenCount\": 150,\"trafficType\": \"ON_DEMAND\",\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 7}],\"candidatesTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": 1}],\"thoughtsTokenCount\": 142},\"modelVersion\": \"gemini-3.5-flash\",\"createTime\": \"2026-08-23T17:21:48.528714Z\",\"responseId\": \"LCyLasqiIO6crb8P1sDboQc\"}\r\n\r\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"model": "jev-1.13-free",
|
||||
"tags": [
|
||||
"prefix:opencode-zen-evaluation",
|
||||
"provider:opencode",
|
||||
"protocol:system-one"
|
||||
],
|
||||
"name": "opencode-zen-evaluation/evaluates-choice-score-and-boolean-questions",
|
||||
"recordedAt": "2026-09-22T03:44:57.511Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://opencode.ai/zen/v1/systemone",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"jev-1.13-free\",\"state\":\"I was charged twice for the same invoice. Please refund the duplicate payment today.\",\"questions\":{\"department\":{\"type\":\"choice\",\"instructions\":\"Which team should handle this support request?\",\"criteria\":{\"billing\":\"Payments, invoices, refunds, or failed charges\",\"technical\":\"Bugs, outages, or integrations\",\"sales\":\"Pricing, upgrades, or new accounts\"}},\"urgency\":{\"type\":\"score\",\"instructions\":\"How urgent is this support request?\",\"criteria\":[\"Can wait for normal support\",\"Needs prompt attention\",\"Actively blocking revenue\"]},\"refund\":{\"type\":\"noul\",\"instructions\":\"Is the customer asking for a refund?\"}}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"jev-1.13-free\",\"answers\":{\"department\":{\"type\":\"choice\",\"choice\":\"billing\",\"confidence\":1,\"probabilities\":{\"technical\":0,\"sales\":0,\"billing\":1}},\"urgency\":{\"type\":\"score\",\"score\":1.02,\"confidence\":0.95,\"legend\":{\"0\":\"Can wait for normal support\",\"1\":\"Needs prompt attention\",\"2\":\"Actively blocking revenue\"},\"probabilities\":{\"0\":0,\"1\":0.97,\"2\":0.03}},\"refund\":{\"type\":\"noul\",\"noul\":0.99}},\"usage\":{\"input_tokens\":422,\"output_tokens\":69}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"model": "jev-latest",
|
||||
"tags": [
|
||||
"prefix:typesafe-evaluation",
|
||||
"provider:typesafe-ai",
|
||||
"protocol:system-one"
|
||||
],
|
||||
"name": "typesafe-evaluation/evaluates-choice-score-and-boolean-questions",
|
||||
"recordedAt": "2026-09-22T03:44:28.626Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"transport": "http",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"url": "https://api.typesafe.ai/v1/systemone",
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"jev-latest\",\"state\":\"I was charged twice for the same invoice. Please refund the duplicate payment today.\",\"questions\":{\"department\":{\"type\":\"choice\",\"instructions\":\"Which team should handle this support request?\",\"criteria\":{\"billing\":\"Payments, invoices, refunds, or failed charges\",\"technical\":\"Bugs, outages, or integrations\",\"sales\":\"Pricing, upgrades, or new accounts\"}},\"urgency\":{\"type\":\"score\",\"instructions\":\"How urgent is this support request?\",\"criteria\":[\"Can wait for normal support\",\"Needs prompt attention\",\"Actively blocking revenue\"]},\"refund\":{\"type\":\"noul\",\"instructions\":\"Is the customer asking for a refund?\"}}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"jev-1.13.0\",\"answers\":{\"department\":{\"type\":\"choice\",\"choice\":\"billing\",\"confidence\":1.0,\"probabilities\":{\"billing\":1.0,\"sales\":0.0,\"technical\":0.0}},\"urgency\":{\"type\":\"score\",\"score\":1.01,\"confidence\":0.97,\"legend\":{\"0\":\"Can wait for normal support\",\"1\":\"Needs prompt attention\",\"2\":\"Actively blocking revenue\"},\"probabilities\":{\"0\":0.0,\"1\":0.98,\"2\":0.02}},\"refund\":{\"type\":\"noul\",\"noul\":0.99}},\"usage\":{\"input_tokens\":422,\"output_tokens\":69}}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { Evaluation } from "../../src/experimental.js"
|
||||
import { OpenCodeZen, TypeSafeAI } from "../../src/providers.js"
|
||||
import { recordedTests } from "../recorded-test.js"
|
||||
|
||||
const questions = {
|
||||
department: {
|
||||
type: "choice",
|
||||
instructions: "Which team should handle this support request?",
|
||||
criteria: {
|
||||
billing: "Payments, invoices, refunds, or failed charges",
|
||||
technical: "Bugs, outages, or integrations",
|
||||
sales: "Pricing, upgrades, or new accounts",
|
||||
},
|
||||
},
|
||||
urgency: {
|
||||
type: "score",
|
||||
instructions: "How urgent is this support request?",
|
||||
criteria: ["Can wait for normal support", "Needs prompt attention", "Actively blocking revenue"],
|
||||
},
|
||||
refund: { type: "boolean", instructions: "Is the customer asking for a refund?" },
|
||||
} as const
|
||||
|
||||
const state = "I was charged twice for the same invoice. Please refund the duplicate payment today."
|
||||
|
||||
const typesafe = recordedTests({
|
||||
prefix: "typesafe-evaluation",
|
||||
provider: "typesafe-ai",
|
||||
protocol: "system-one",
|
||||
requires: ["TYPESAFE_API_KEY"],
|
||||
metadata: { model: "jev-latest" },
|
||||
})
|
||||
|
||||
const zen = recordedTests({
|
||||
prefix: "opencode-zen-evaluation",
|
||||
provider: "opencode",
|
||||
protocol: "system-one",
|
||||
requires: ["OPENCODE_API_KEY"],
|
||||
metadata: { model: "jev-1.13-free" },
|
||||
})
|
||||
|
||||
describe("experimental Evaluation recorded", () => {
|
||||
typesafe.effect("evaluates choice score and boolean questions", () =>
|
||||
assertEvaluation(
|
||||
TypeSafeAI.configure({ apiKey: process.env.TYPESAFE_API_KEY ?? "fixture" }).experimental.evaluation("jev-latest"),
|
||||
"typesafe",
|
||||
),
|
||||
)
|
||||
|
||||
zen.effect("evaluates choice score and boolean questions", () =>
|
||||
assertEvaluation(
|
||||
OpenCodeZen.configure({ apiKey: process.env.OPENCODE_API_KEY ?? "fixture" }).experimental.evaluation(
|
||||
"jev-1.13-free",
|
||||
),
|
||||
"opencode",
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
const assertEvaluation = (
|
||||
model: ReturnType<typeof TypeSafeAI.experimental.evaluation>,
|
||||
metadataKey: "typesafe" | "opencode",
|
||||
) =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* Evaluation.evaluate({ model, state, questions })
|
||||
expect(response.model).toStartWith("jev-")
|
||||
expect(response.answers.department.type).toBe("choice")
|
||||
expect(response.answers.department.choice).toBe("billing")
|
||||
expect(response.answers.department.probabilities?.billing).toBeGreaterThan(0.5)
|
||||
expect(response.answers.urgency.type).toBe("score")
|
||||
expect(response.answers.urgency.score).toBeGreaterThanOrEqual(0)
|
||||
expect(response.answers.urgency.score).toBeLessThanOrEqual(2)
|
||||
expect(response.answers.refund.type).toBe("boolean")
|
||||
expect(response.answers.refund.probability).toBeGreaterThan(0.5)
|
||||
expect(response.usage?.inputTokens).toBeGreaterThan(0)
|
||||
expect(response.usage?.outputTokens).toBeGreaterThan(0)
|
||||
expect(response.providerMetadata?.[metadataKey]?.confidence).toBeDefined()
|
||||
})
|
||||
@@ -114,7 +114,7 @@ describe("Google Vertex providers", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("preserves function call ids in lowered Vertex bodies", () =>
|
||||
it.effect("strips function call ids Vertex does not accept from lowered bodies", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
@@ -142,14 +142,15 @@ describe("Google Vertex providers", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
expect(JSON.stringify(prepared.body.contents)).not.toContain('"id"')
|
||||
expect(prepared.body.contents).toMatchObject([
|
||||
{ role: "model", parts: [{ functionCall: { id: "call_1", name: "lookup", args: { query: "weather" } } }] },
|
||||
{ role: "model", parts: [{ functionCall: { id: undefined, name: "lookup", args: { query: "weather" } } }] },
|
||||
{
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
id: "call_1",
|
||||
id: undefined,
|
||||
name: "lookup",
|
||||
response: { name: "lookup", content: "sunny" },
|
||||
},
|
||||
@@ -234,23 +235,12 @@ describe("Google Vertex providers", () => {
|
||||
parts: [
|
||||
{ text: "Thinking.", thought: true, thoughtSignature: "reasoning_sig" },
|
||||
{ text: "Checking.", thoughtSignature: "text_sig" },
|
||||
{
|
||||
functionCall: { id: "provider_call_1", name: "lookup", args: { query: "weather" } },
|
||||
thoughtSignature: "tool_sig",
|
||||
},
|
||||
{ functionCall: { name: "lookup", args: { query: "weather" } }, thoughtSignature: "tool_sig" },
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
parts: [
|
||||
{
|
||||
functionResponse: {
|
||||
id: "provider_call_1",
|
||||
name: "lookup",
|
||||
response: { name: "lookup", content: "sunny" },
|
||||
},
|
||||
},
|
||||
],
|
||||
parts: [{ functionResponse: { name: "lookup", response: { name: "lookup", content: "sunny" } } }],
|
||||
},
|
||||
])
|
||||
}),
|
||||
|
||||
@@ -6,8 +6,6 @@ import * as path from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
import { LLMClient, RequestExecutor } from "../src/route.js"
|
||||
import { ImageClient } from "../src/image-client.js"
|
||||
import { EvaluationClient } from "../src/experimental/evaluation-client.js"
|
||||
import type { Service as EvaluationClientService } from "../src/experimental/evaluation-client.js"
|
||||
import type { Service as ImageClientService } from "../src/image-client.js"
|
||||
import type { Service as LLMClientService } from "../src/route/client.js"
|
||||
import type { Service as RequestExecutorService } from "../src/route/executor.js"
|
||||
@@ -20,12 +18,7 @@ import {
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const FIXTURES_DIR = path.resolve(__dirname, "fixtures", "recordings")
|
||||
|
||||
type RecordedEnv =
|
||||
| RequestExecutorService
|
||||
| LLMClientService
|
||||
| ImageClientService
|
||||
| EvaluationClientService
|
||||
| Socket.WebSocketConstructor
|
||||
type RecordedEnv = RequestExecutorService | LLMClientService | ImageClientService | Socket.WebSocketConstructor
|
||||
|
||||
type RecordedTestsOptions = RecordedGroupOptions & {
|
||||
readonly options?: HttpRecorder.RecorderOptions
|
||||
@@ -99,7 +92,6 @@ export const recordedTests = (options: RecordedTestsOptions) =>
|
||||
requestExecutor,
|
||||
LLMClient.layer.pipe(Layer.provide(requestExecutor)),
|
||||
ImageClient.layer.pipe(Layer.provide(requestExecutor)),
|
||||
EvaluationClient.layer.pipe(Layer.provide(requestExecutor)),
|
||||
webSocket,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -703,7 +703,7 @@ function messageContent(
|
||||
return {
|
||||
type: "reasoning",
|
||||
text: part.text,
|
||||
native: jsonRecord(part.metadata),
|
||||
state: jsonRecord(part.metadata),
|
||||
time: part.time
|
||||
? { created: part.time.start, ...(part.time.end === undefined ? {} : { completed: part.time.end }) }
|
||||
: undefined,
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
}
|
||||
|
||||
[data-slot="session-side-region-presence"][data-opened="false"] {
|
||||
animation: side-region-presence-out 240ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
animation: side-region-presence-out 240ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
[data-slot="terminal-panel-presence"][data-opened="true"] {
|
||||
|
||||
@@ -55,7 +55,7 @@ try {
|
||||
if ((await pluginIDs(info.url, headers)).includes("smoke")) throw new Error("Smoke plugin existed before creation")
|
||||
const plugin = path.join(root, ".opencode", "plugins", "smoke.ts")
|
||||
await fs.writeFile(plugin, pluginSource())
|
||||
await waitForPlugin(info.url, headers, plugin)
|
||||
await waitForPlugin(info.url, headers)
|
||||
|
||||
const unauthorizedInfo = await fetch(new URL("/api/info", info.url), {
|
||||
signal: AbortSignal.timeout(5_000),
|
||||
@@ -139,13 +139,7 @@ async function waitForReady(url: string, headers: HeadersInit) {
|
||||
}
|
||||
|
||||
function exitsWithin(process: Bun.Subprocess, milliseconds: number) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const timeout = setTimeout(() => resolve(false), milliseconds)
|
||||
process.exited.then(() => {
|
||||
clearTimeout(timeout)
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
return Promise.race([process.exited.then(() => true), Bun.sleep(milliseconds).then(() => false)])
|
||||
}
|
||||
|
||||
function pluginSource() {
|
||||
@@ -165,15 +159,11 @@ async function pluginIDs(url: string, headers: HeadersInit) {
|
||||
)
|
||||
}
|
||||
|
||||
async function waitForPlugin(url: string, headers: HeadersInit, plugin: string) {
|
||||
async function waitForPlugin(url: string, headers: HeadersInit) {
|
||||
const deadline = Date.now() + 10_000
|
||||
let attempt = 0
|
||||
while (Date.now() < deadline) {
|
||||
if ((await pluginIDs(url, headers)).includes("smoke")) return
|
||||
await Bun.sleep(25)
|
||||
// Native watchers may coalesce a single creation edge. Keep changing valid source so
|
||||
// the smoke proves that a later native event is delivered.
|
||||
if (++attempt % 10 === 0) await fs.writeFile(plugin, `${pluginSource()}// watcher retry ${attempt}\n`)
|
||||
}
|
||||
throw new Error("Compiled service did not discover the created plugin")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { OpenCode } from "@opencode/client"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { Session } from "@opencode/schema/session"
|
||||
import { SessionMessage } from "@opencode/schema/session-message"
|
||||
import { SessionTransfer } from "@opencode/schema/session-transfer"
|
||||
import { Effect, Option, Predicate, Schema } from "effect"
|
||||
import { Effect, Option, Schema } from "effect"
|
||||
import { EOL } from "node:os"
|
||||
import path from "node:path"
|
||||
import { Commands } from "../../commands"
|
||||
@@ -24,13 +23,7 @@ export default Runtime.handler(
|
||||
catch: (cause) =>
|
||||
new Error(`Failed to read session data: ${cause instanceof Error ? cause.message : String(cause)}`),
|
||||
})
|
||||
const raw = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(text)
|
||||
// Exports written before provider blobs were renamed to `native` still carry the old keys.
|
||||
const data = yield* Schema.decodeUnknownEffect(SessionTransfer.Data)(
|
||||
Predicate.isObject(raw) && Array.isArray(raw.messages)
|
||||
? { ...raw, messages: raw.messages.map(SessionMessage.persisted) }
|
||||
: raw,
|
||||
)
|
||||
const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(SessionTransfer.Data))(text)
|
||||
const encoded = Schema.encodeSync(SessionTransfer.Data)(data)
|
||||
const server = yield* ServerConnection.resolve({
|
||||
server: Option.getOrUndefined(input.server),
|
||||
|
||||
@@ -7,7 +7,7 @@ import { produce, type Draft } from "immer"
|
||||
import { applyEdits, modify, parse, type ParseError } from "jsonc-parser"
|
||||
import path from "path"
|
||||
import { ConfigMigration } from "./migrate"
|
||||
import { Info, normalizeLegacyTabs, SchemaURL } from "./schema"
|
||||
import { Info, SchemaURL } from "./schema"
|
||||
|
||||
export * from "./schema"
|
||||
|
||||
@@ -119,7 +119,7 @@ function merge(...values: readonly (Info | undefined)[]) {
|
||||
return Option.getOrElse(
|
||||
decode(
|
||||
values.reduce<Record<string, unknown>>(
|
||||
(result, value) => mergeRecords(result, normalizeLegacyTabs(value) ?? {}),
|
||||
(result, value) => mergeRecords(result, value ?? {}),
|
||||
{},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -8,11 +8,3 @@ export const Info = Schema.Struct({
|
||||
...Config.Info.fields,
|
||||
})
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
|
||||
export function normalizeLegacyTabs(info: Info | undefined) {
|
||||
if (info?.tabs?.enabled === undefined) return info
|
||||
const tabs = { ...info.tabs }
|
||||
tabs.mode ??= tabs.enabled ? "on" : "off"
|
||||
delete tabs.enabled
|
||||
return { ...info, tabs }
|
||||
}
|
||||
|
||||
@@ -564,7 +564,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
messageID: message.id,
|
||||
type: "reasoning",
|
||||
text,
|
||||
metadata: item.native,
|
||||
metadata: item.state,
|
||||
time: { start: message.time.created, end: timestamp },
|
||||
}
|
||||
renderedReasoning.set(key, item.text)
|
||||
|
||||
@@ -78,7 +78,7 @@ test("merges inline CLI config content over the global config", async () => {
|
||||
await Bun.write(
|
||||
file,
|
||||
JSON.stringify({
|
||||
tabs: { mode: "on", scope: "global" },
|
||||
tabs: { enabled: true, scope: "global" },
|
||||
keybinds: { "app.exit": "ctrl+q" },
|
||||
plugins: ["global"],
|
||||
animations: true,
|
||||
@@ -105,7 +105,7 @@ test("merges inline CLI config content over the global config", async () => {
|
||||
}),
|
||||
)
|
||||
|
||||
expect(result.loaded.tabs).toEqual({ mode: "off", scope: "global" })
|
||||
expect(result.loaded.tabs).toEqual({ enabled: false, scope: "global" })
|
||||
expect(result.loaded.keybinds).toEqual({ "app.exit": "ctrl+q", "help.show": false })
|
||||
expect(result.loaded.plugins).toEqual(["inline"])
|
||||
expect(result.updated).toMatchObject({ animations: false, mouse: false })
|
||||
@@ -116,26 +116,6 @@ test("merges inline CLI config content over the global config", async () => {
|
||||
}
|
||||
})
|
||||
|
||||
test("reads the legacy tabs toggle without rewriting it", async () => {
|
||||
await using directory = await tmpdir()
|
||||
const file = path.join(directory.path, "cli.json")
|
||||
await Bun.write(file, JSON.stringify({ tabs: { enabled: false } }))
|
||||
|
||||
const config = await run(
|
||||
directory.path,
|
||||
Effect.gen(function* () {
|
||||
const service = yield* Config.Service
|
||||
expect((yield* service.get()).tabs).toEqual({ mode: "off" })
|
||||
return yield* service.update((draft) => {
|
||||
draft.animations = false
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
expect(config.tabs).toEqual({ mode: "off" })
|
||||
expect(await Bun.file(file).json()).toEqual({ tabs: { enabled: false }, animations: false })
|
||||
})
|
||||
|
||||
test("migrates tui and kv config into cli.json", async () => {
|
||||
await using directory = await tmpdir()
|
||||
await Bun.write(
|
||||
|
||||
@@ -509,12 +509,12 @@ export type PromptAgentAttachment = { name: string; mention?: PromptMention }
|
||||
|
||||
export type PromptSkillAttachment = { id: string; name: string; text?: string; mention?: PromptMention }
|
||||
|
||||
export type SessionMessageAssistantText = { type: "text"; text: string; native?: SessionMessageProviderState }
|
||||
export type SessionMessageAssistantText = { type: "text"; text: string; state?: SessionMessageProviderState }
|
||||
|
||||
export type SessionMessageAssistantReasoning = {
|
||||
type: "reasoning"
|
||||
text: string
|
||||
native?: SessionMessageProviderState
|
||||
state?: SessionMessageProviderState
|
||||
time?: { created: number; completed?: number }
|
||||
}
|
||||
|
||||
@@ -1354,6 +1354,15 @@ export type SessionToolCalled = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantText1 = { type: "text"; text: string; state?: SessionMessageProviderState1 }
|
||||
|
||||
export type SessionMessageAssistantReasoning1 = {
|
||||
type: "reasoning"
|
||||
text: string
|
||||
state?: SessionMessageProviderState1
|
||||
time?: { created: number; completed?: number }
|
||||
}
|
||||
|
||||
export type ToolContent1 = ToolTextContent | ToolFileContent1
|
||||
|
||||
export type FormNumberField = {
|
||||
@@ -1753,7 +1762,7 @@ export type SessionMessageCompactionCompleted = {
|
||||
status: "completed"
|
||||
reason: "auto" | "manual"
|
||||
model?: ModelRef
|
||||
native?: SessionMessageProviderState
|
||||
providerState?: SessionMessageProviderState
|
||||
summary: string
|
||||
recent: string
|
||||
providerContext?: SessionProviderContext
|
||||
@@ -2219,7 +2228,7 @@ export type SessionMessageAssistant = {
|
||||
snapshot?: { start?: string; end?: string; files?: Array<string> }
|
||||
finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
rawFinish?: string
|
||||
native?: SessionMessageProviderState
|
||||
providerState?: SessionMessageProviderState
|
||||
cost?: MoneyUSD
|
||||
tokens?: TokenUsageInfo
|
||||
error?: SessionStructuredError
|
||||
@@ -2227,13 +2236,8 @@ export type SessionMessageAssistant = {
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantContentEncoded =
|
||||
| { type: "text"; text: string; state?: SessionMessageProviderState1 }
|
||||
| {
|
||||
type: "reasoning"
|
||||
text: string
|
||||
time?: { created: number; completed?: number }
|
||||
state?: SessionMessageProviderState1
|
||||
}
|
||||
| SessionMessageAssistantText1
|
||||
| SessionMessageAssistantReasoning1
|
||||
| SessionMessageAssistantTool1
|
||||
|
||||
export type FormInfo = { id: string; sessionID: string; title: string; metadata?: FormMetadata; fields: FormFields }
|
||||
@@ -3098,11 +3102,11 @@ export type SessionImportInput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string; readonly native?: { readonly [x: string]: JsonValue } }
|
||||
| { readonly type: "text"; readonly text: string; readonly state?: { readonly [x: string]: JsonValue } }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
@@ -3176,7 +3180,7 @@ export type SessionImportInput = {
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3210,7 +3214,7 @@ export type SessionImportInput = {
|
||||
readonly status: "completed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly providerContext?: {
|
||||
@@ -3415,11 +3419,11 @@ export type SessionImportInput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string; readonly native?: { readonly [x: string]: JsonValue } }
|
||||
| { readonly type: "text"; readonly text: string; readonly state?: { readonly [x: string]: JsonValue } }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
@@ -3493,7 +3497,7 @@ export type SessionImportInput = {
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3527,7 +3531,7 @@ export type SessionImportInput = {
|
||||
readonly status: "completed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly providerContext?: {
|
||||
@@ -3732,11 +3736,11 @@ export type SessionImportInput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string; readonly native?: { readonly [x: string]: JsonValue } }
|
||||
| { readonly type: "text"; readonly text: string; readonly state?: { readonly [x: string]: JsonValue } }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
@@ -3810,7 +3814,7 @@ export type SessionImportInput = {
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3844,7 +3848,7 @@ export type SessionImportInput = {
|
||||
readonly status: "completed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly providerContext?: {
|
||||
|
||||
@@ -846,7 +846,7 @@ export function createData(config: CreateDataInput) {
|
||||
existing.error = undefined
|
||||
existing.finish = undefined
|
||||
existing.rawFinish = undefined
|
||||
existing.native = undefined
|
||||
existing.providerState = undefined
|
||||
existing.time.created = event.data.started
|
||||
existing.time.streamed = undefined
|
||||
existing.time.completed = undefined
|
||||
@@ -880,7 +880,7 @@ export function createData(config: CreateDataInput) {
|
||||
assistant.time.completed = event.created
|
||||
assistant.finish = event.data.finish
|
||||
assistant.rawFinish = event.data.rawFinish
|
||||
assistant.native = event.data.providerState
|
||||
assistant.providerState = event.data.providerState
|
||||
assistant.cost = event.data.cost
|
||||
assistant.tokens = event.data.tokens
|
||||
if (event.data.snapshot) assistant.snapshot = { ...assistant.snapshot, end: event.data.snapshot }
|
||||
@@ -892,7 +892,7 @@ export function createData(config: CreateDataInput) {
|
||||
assistant.time.completed = event.created
|
||||
assistant.finish = event.data.finish ?? "error"
|
||||
assistant.rawFinish = event.data.rawFinish
|
||||
assistant.native = event.data.providerState
|
||||
assistant.providerState = event.data.providerState
|
||||
assistant.error = event.data.error
|
||||
assistant.retry = undefined
|
||||
if (event.data.cost !== undefined && event.data.tokens !== undefined) {
|
||||
@@ -914,7 +914,6 @@ export function createData(config: CreateDataInput) {
|
||||
case "session.text.ended":
|
||||
message.editText(event.data.sessionID, event.data.assistantMessageID, (text) => {
|
||||
text.text = event.data.text
|
||||
text.native = event.data.state
|
||||
})
|
||||
return
|
||||
case "session.tool.input.started":
|
||||
@@ -985,7 +984,7 @@ export function createData(config: CreateDataInput) {
|
||||
assistant.content.push({
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
native: event.data.state,
|
||||
state: event.data.state,
|
||||
time: { created: event.created },
|
||||
})
|
||||
})
|
||||
@@ -999,7 +998,7 @@ export function createData(config: CreateDataInput) {
|
||||
message.editReasoning(event.data.sessionID, event.data.assistantMessageID, (reasoning) => {
|
||||
reasoning.text = event.data.text
|
||||
reasoning.time = { created: reasoning.time?.created ?? event.created, completed: event.created }
|
||||
if (event.data.state !== undefined) reasoning.native = event.data.state
|
||||
if (event.data.state !== undefined) reasoning.state = event.data.state
|
||||
})
|
||||
return
|
||||
case "session.retry.scheduled":
|
||||
@@ -1106,7 +1105,7 @@ export function createData(config: CreateDataInput) {
|
||||
status: "completed",
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
native: event.data.providerState,
|
||||
providerState: event.data.providerState,
|
||||
providerContext: event.data.providerContext,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
@@ -1121,7 +1120,7 @@ export function createData(config: CreateDataInput) {
|
||||
status: "completed",
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
native: event.data.providerState,
|
||||
providerState: event.data.providerState,
|
||||
providerContext: event.data.providerContext,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
|
||||
@@ -135,7 +135,7 @@ test.each(["started", "cancelled", "failed"])(
|
||||
status: "completed",
|
||||
summary: "Summary",
|
||||
model,
|
||||
native: providerState,
|
||||
providerState,
|
||||
providerContext,
|
||||
cost: 0.01,
|
||||
tokens,
|
||||
|
||||
@@ -124,8 +124,8 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
- [x] `Boolean`, `Number`, `String`, `parseInt`, `parseFloat`, `isFinite`, `isNaN`, and URI helpers as callbacks.
|
||||
- [x] Built-in method references as callbacks, such as `values.map(Math.abs)`, `records.map(JSON.stringify)`,
|
||||
`items.forEach(console.log)`, and `Promise.resolve(-1).then(Math.abs)`. Extra callback arguments a built-in
|
||||
does not consume are ignored, like JS, and consumed arguments coerce, like JS (`"3.7".replace(/\d\.\d/,
|
||||
Math.floor)` is `"3"`). A detached method loses its receiver, as in JS: `values.filter("abc".includes)` is a `TypeError`
|
||||
does not consume are ignored, like JS; consumed arguments stay strictly validated (`Math.floor` still rejects a
|
||||
string). A detached method loses its receiver, as in JS: `values.filter("abc".includes)` is a `TypeError`
|
||||
because `includes` is called without a string `this`.
|
||||
- [x] Constructors work as callbacks with JS call semantics: `Error` types construct (`messages.map(Error)`),
|
||||
and new-requiring constructors (`Map`, `Set`, `URL`, `URLSearchParams`, `Headers`, `Promise`) throw a `TypeError`,
|
||||
@@ -145,13 +145,10 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
assignments, object literal keys, and destructuring or parameter defaults.
|
||||
- [x] Built-in functions are objects too, with `name` and `length` (`Math.max.length === 2`,
|
||||
`Array.prototype.push.name === "push"`).
|
||||
- [x] A named function expression's name is bound read-only inside its own body; assigning to it throws a
|
||||
`TypeError`, as in strict mode.
|
||||
- [x] Redeclaring a function in the same scope, or alongside a `var`, is allowed: the last declaration wins.
|
||||
- [x] Generator functions have their own `prototype` (inheriting the shared generator prototype), so
|
||||
`g() instanceof g` holds. Plain functions have none, since they cannot construct.
|
||||
- [x] Generator and async generator functions bind parameters (defaults, destructuring) at the call and defer only the
|
||||
body to the first `next()`, so a bad argument throws synchronously from the call site, as in JS.
|
||||
- [ ] A named function expression's name is not bound inside its own body.
|
||||
- [ ] Redeclaring a function in the same scope is rejected; in JavaScript the last declaration wins.
|
||||
- [ ] Generator and async generator functions evaluate parameter defaults and destructuring at the first `next()`
|
||||
rather than at the call, so their errors are not thrown synchronously.
|
||||
- [x] Synchronous and async generator declarations/expressions, `yield`, and `yield*`, including lazy bodies,
|
||||
`next(value)`, `return(value)`, `throw(value)`, exhaustion, promise adoption, async request ordering,
|
||||
`try`/`catch`/`finally`, and sync/async iterator symbols. Async `yield*` awaits values while adapting a sync
|
||||
@@ -199,14 +196,13 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
- [x] Plain, arithmetic, bitwise, and logical assignment operators.
|
||||
- [x] Property deletion on plain data objects and arrays, including computed and optional forms; deleting an array index
|
||||
creates a hole without changing its length. Deleting a non-configurable property (`length`) or
|
||||
assigning a read-only one (`Math.PI`, `fn.name`) throws a `TypeError`, as in strict mode. `delete` of a
|
||||
non-reference (`delete 0`, `delete f()`) evaluates the operand and is `true`; `delete x` on a variable throws.
|
||||
assigning a read-only one (`Math.PI`, `fn.name`) throws a `TypeError`, as in strict mode.
|
||||
- [ ] Operators, `switch` discriminants, template interpolation, and coercion helpers such as `String` and `isNaN`
|
||||
applied to functions and namespaces; JavaScript coerces them, the interpreter rejects non-data operands.
|
||||
- [ ] ToPrimitive on object operands: operators, `Error(message)`, `Date` arguments, and `parseInt` radix should call
|
||||
`valueOf`/`toString` in spec order and surface their throws.
|
||||
- [x] Property keys follow ToPropertyKey: `x[null]`, `x[true]`, and objects (via their built-in string form) become
|
||||
string keys.
|
||||
- [ ] Property keys follow ToPropertyKey: `x[null]`, `x[true]`, and objects (via `toString`) become string keys; only
|
||||
strings and numbers are accepted.
|
||||
|
||||
## Promises and tools
|
||||
|
||||
@@ -243,7 +239,7 @@ ultimate source of truth. Upstream test262 files run verbatim from `test/test262
|
||||
accepted, including `.then`/`.catch` handlers and collection callbacks, and vanish at the data boundary like
|
||||
any function.
|
||||
- [x] `Promise.withResolvers()`: the same promise and resolver callables as the constructor, as a `{ promise, resolve,
|
||||
reject }` object.
|
||||
reject }` object.
|
||||
- [x] `Promise.try(fn, ...args)`: calls `fn` synchronously; a throw rejects, a return fulfils, and a returned promise or
|
||||
thenable is adopted.
|
||||
- [x] Recursive assimilation of objects with an own callable `then` field across `Promise.resolve`, combinators,
|
||||
@@ -266,9 +262,7 @@ reject }` object.
|
||||
not construct.
|
||||
- [x] `Object()` and `new Object()` return `{}` for nullish arguments and pass objects through unchanged;
|
||||
primitive wrapper objects (`Object(1)`) are rejected explicitly.
|
||||
- [x] Computed property names and object spread. Any value works as a key (ToPropertyKey): strings, numbers, and the
|
||||
two confined symbols as themselves, everything else as its string form (`o[null]` is `o["null"]`, `o[{}]` is
|
||||
`o["[object Object]"]`), in reads, writes, literals, `in`, and destructuring.
|
||||
- [x] Computed property names and object spread.
|
||||
- [x] `Object.keys`, `Object.values`, `Object.entries`, `Object.hasOwn`, `Object.assign`, and `Object.fromEntries`, with
|
||||
synchronous iterator support for `fromEntries`. Sources follow ToObject: strings enumerate by index, other
|
||||
primitives and wrappers contribute nothing, and `null`/`undefined` throw. `Object.assign` accepts array
|
||||
@@ -319,12 +313,10 @@ reject }` object.
|
||||
- [x] Assigning `length` to truncate or extend an array; invalid lengths throw `RangeError`.
|
||||
- [x] Non-index own properties on arrays (`arr.foo = 1`, `arr.constructor = null`). They are excluded from the JSON
|
||||
form, like `JSON.stringify`.
|
||||
- [x] Numeric arguments coerce as in JS (ToIntegerOrInfinity): `indexOf(x, "1")`, `slice("1", "3")`, `at(null)`,
|
||||
`flat(1.9)`, `with(1.5, v)`, `Math.max("3", "2")`, `parseInt("11", "2")`, `(1.5).toFixed("2")`,
|
||||
`String.fromCharCode("65")`, and the Uint8Array equivalents. `join(sep)` and `JSON.parse(text)` apply ToString
|
||||
(`join(null)` is `"1null2"`, `JSON.parse(123)` is `123`). `Array.from({ length: "2" })` applies ToLength; a
|
||||
promise source still throws with an `await` hint rather than JS's silent `[]`. A program object's own
|
||||
`valueOf`/`toString` is not consulted yet (see ToPrimitive above).
|
||||
- [ ] Argument coercion for `indexOf`, `lastIndexOf`, `includes`, `fill`, `flat`, `copyWithin`, and the `join`
|
||||
separator: JavaScript applies ToIntegerOrInfinity/ToString (including `valueOf`, strings, and `undefined`), the
|
||||
interpreter requires numbers and strings. `indexOf()` and `lastIndexOf()` with no argument already search for
|
||||
`undefined`; `includes()` still requires a value.
|
||||
|
||||
## Strings
|
||||
|
||||
@@ -445,7 +437,6 @@ reject }` object.
|
||||
- [x] Static `Map.groupBy` over finite collections and custom synchronous iterators/generators, preserving key identity.
|
||||
- [x] `new Map()` from synchronous iterables of entries.
|
||||
- [x] Map `get`, `set`, `has`, `delete`, `clear`, `size`, `forEach`, `getOrInsert`, and `getOrInsertComputed`.
|
||||
`forEach` is live: entries deleted during the walk are skipped and entries added are visited, as in JS.
|
||||
- [x] `new Set()` from synchronous iterables.
|
||||
- [x] Set `add`, `has`, `delete`, `clear`, `size`, and `forEach`.
|
||||
- [x] Live `keys`, `values`, `entries`, and `[Symbol.iterator]` iterators for Map and Set; a Set-like operand's `keys()`
|
||||
|
||||
@@ -74,7 +74,6 @@ import {
|
||||
IteratorObj,
|
||||
type Cursor,
|
||||
has,
|
||||
hidden,
|
||||
hasPrototype,
|
||||
keys,
|
||||
Native,
|
||||
@@ -455,9 +454,8 @@ class Frame<R> {
|
||||
node: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression,
|
||||
name = node.type === "ArrowFunctionExpression" ? "" : (node.id?.name ?? ""),
|
||||
): Fn {
|
||||
const builtins = this.ctx.builtins
|
||||
const fn = new Fn(
|
||||
builtins.Function,
|
||||
return new Fn(
|
||||
this.ctx.builtins.Function,
|
||||
name,
|
||||
node.params,
|
||||
node.body,
|
||||
@@ -465,14 +463,6 @@ class Frame<R> {
|
||||
node.async,
|
||||
node.generator,
|
||||
)
|
||||
// Each generator function gets its own prototype, so `g() instanceof g` holds as in JS.
|
||||
if (node.generator)
|
||||
define(fn, "prototype", new Obj(node.async ? builtins.AsyncGenerator : builtins.Generator), hidden)
|
||||
// The body of a named function expression sees its own name, read-only.
|
||||
if (node.type === "FunctionExpression" && node.id) {
|
||||
fn.capturedScopes.push(new Map([[node.id.name, { mutable: false, value: fn, initialized: true }]]))
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// NamedEvaluation: an anonymous function definition takes the name of what it is assigned to.
|
||||
@@ -483,11 +473,10 @@ class Frame<R> {
|
||||
return this.evaluateExpression(node)
|
||||
}
|
||||
|
||||
// Repeated `function` declarations and `var` clashes are legal: the last declaration wins.
|
||||
private hoistFunctions(statements: ReadonlyArray<Statement | ModuleDeclaration>): void {
|
||||
for (const node of statements) {
|
||||
if (node.type !== "FunctionDeclaration") continue
|
||||
this.scopes.current().set(node.id.name, { mutable: true, value: this.createFunction(node), initialized: true })
|
||||
this.scopes.declare(node.id.name, this.createFunction(node), true, node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1244,7 +1233,7 @@ class Frame<R> {
|
||||
}
|
||||
const keyNode = property.key
|
||||
if (property.computed) {
|
||||
return Effect.map(this.evaluateExpression(keyNode), (value) => this.toPropertyKey(value))
|
||||
return Effect.map(this.evaluateExpression(keyNode), (value) => this.toPropertyKey(value, keyNode))
|
||||
}
|
||||
if (keyNode.type === "Identifier") return Effect.succeed(keyNode.name)
|
||||
if (keyNode.type === "Literal") return Effect.succeed(String(keyNode.value))
|
||||
@@ -1659,14 +1648,14 @@ class Frame<R> {
|
||||
const depth = Math.max(self.depth, site.depth) + 1
|
||||
if (depth > MAX_CALL_DEPTH) throw rangeError("Maximum call stack size exceeded", node)
|
||||
const invocation = new Frame(this.ctx, new ScopeStack([...fn.capturedScopes, new Map()]), depth)
|
||||
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
||||
const paramScope = invocation.scopes.current()
|
||||
for (const parameter of fn.parameters) {
|
||||
for (const name of collectPatternNames(parameter)) {
|
||||
paramScope.set(name, { mutable: true, value: undefined, initialized: false })
|
||||
const run = Effect.gen(function* () {
|
||||
// Seed all parameters first so defaults cannot fall through to same-named outer bindings.
|
||||
const paramScope = invocation.scopes.current()
|
||||
for (const parameter of fn.parameters) {
|
||||
for (const name of collectPatternNames(parameter)) {
|
||||
paramScope.set(name, { mutable: true, value: undefined, initialized: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
const bind = Effect.gen(function* () {
|
||||
for (const [index, parameter] of fn.parameters.entries()) {
|
||||
if (parameter.type === "RestElement") {
|
||||
yield* invocation.declarePattern(
|
||||
@@ -1680,8 +1669,7 @@ class Frame<R> {
|
||||
}
|
||||
yield* invocation.declarePattern(parameter, args[index], true, parameter, true)
|
||||
}
|
||||
})
|
||||
const body = Effect.gen(function* () {
|
||||
|
||||
if (fn.body.type === "BlockStatement") {
|
||||
invocation.scopes.push()
|
||||
invocation.hoistVars(fn.body.body, paramScope)
|
||||
@@ -1691,9 +1679,7 @@ class Frame<R> {
|
||||
|
||||
return yield* invocation.evaluateExpression(fn.body)
|
||||
})
|
||||
// Generators bind parameters at the call and defer only the body to the first `next()`, as in JS.
|
||||
if (fn.generator) return Effect.map(bind, () => this.createGenerator(invocation, body, fn))
|
||||
const run = Effect.andThen(bind, body)
|
||||
if (fn.generator) return Effect.succeed(this.createGenerator(invocation, run, fn.async))
|
||||
if (!fn.async) return run
|
||||
return this.ctx.pending.createWithSelf((self) =>
|
||||
Effect.flatMap(run, (value) => resolvePromiseValue(invocation.ctx, value, self)),
|
||||
@@ -1701,8 +1687,11 @@ class Frame<R> {
|
||||
})
|
||||
}
|
||||
|
||||
private createGenerator(invocation: Frame<R>, run: Effect.Effect<Value, unknown, R>, fn: Fn): GeneratorObj {
|
||||
const asynchronous = fn.async
|
||||
private createGenerator(
|
||||
invocation: Frame<R>,
|
||||
run: Effect.Effect<Value, unknown, R>,
|
||||
asynchronous: boolean,
|
||||
): GeneratorObj {
|
||||
const state: GeneratorState = { started: false, completed: false, draining: false, pending: [], pendingIndex: 0 }
|
||||
invocation.generatorState = state
|
||||
invocation.generatorAsync = asynchronous
|
||||
@@ -1770,8 +1759,12 @@ class Frame<R> {
|
||||
}
|
||||
return Deferred.await(request.response)
|
||||
}
|
||||
const proto = get(fn, "prototype")
|
||||
return new GeneratorObj(proto instanceof Obj ? proto : builtins.Generator, asynchronous, request)
|
||||
const generator = new GeneratorObj(
|
||||
asynchronous ? builtins.AsyncGenerator : builtins.Generator,
|
||||
asynchronous,
|
||||
request,
|
||||
)
|
||||
return generator
|
||||
}
|
||||
|
||||
private completeGeneratorRequests(state: GeneratorState, asynchronous: boolean): Effect.Effect<void, never, R> {
|
||||
@@ -1946,11 +1939,11 @@ class Frame<R> {
|
||||
let key: PropertyKey
|
||||
|
||||
if (property.computed) {
|
||||
key = self.toPropertyKey(yield* self.evaluateExpression(keyNode))
|
||||
key = self.toPropertyKey(yield* self.evaluateExpression(keyNode), keyNode)
|
||||
} else if (keyNode.type === "Identifier") {
|
||||
key = keyNode.name
|
||||
} else if (keyNode.type === "Literal") {
|
||||
key = self.toPropertyKey(literal(keyNode))
|
||||
key = self.toPropertyKey(literal(keyNode), keyNode)
|
||||
} else {
|
||||
throw typeError("Unsupported object property key shape.", keyNode)
|
||||
}
|
||||
@@ -2051,10 +2044,10 @@ class Frame<R> {
|
||||
if ((objectValue === null || objectValue === undefined) && node.optional) return OptionalShortCircuit
|
||||
|
||||
const key = node.computed
|
||||
? self.toPropertyKey(yield* self.evaluateExpression(propertyNode))
|
||||
? self.toPropertyKey(yield* self.evaluateExpression(propertyNode), propertyNode)
|
||||
: propertyNode.type === "Identifier"
|
||||
? propertyNode.name
|
||||
: self.toPropertyKey(yield* self.evaluateExpression(propertyNode))
|
||||
: self.toPropertyKey(yield* self.evaluateExpression(propertyNode), propertyNode)
|
||||
|
||||
if (objectValue instanceof ToolReference) {
|
||||
if (typeof key !== "string") {
|
||||
@@ -2116,9 +2109,9 @@ class Frame<R> {
|
||||
|
||||
private evaluateDeleteExpression(argument: Expression): Effect.Effect<boolean, unknown, R> {
|
||||
const target = argument.type === "ChainExpression" ? argument.expression : argument
|
||||
if (target.type === "Identifier") throw typeError("Only data fields may be deleted.", argument)
|
||||
// `delete <non-reference>` evaluates the operand and is true, as in JS.
|
||||
if (target.type !== "MemberExpression") return Effect.map(this.evaluateExpression(target), () => true)
|
||||
if (target.type !== "MemberExpression") {
|
||||
throw typeError("Only data fields may be deleted.", argument)
|
||||
}
|
||||
return Effect.map(this.getMemberReference(target), (reference) => {
|
||||
if (reference === OptionalShortCircuit) return true
|
||||
if (reference instanceof ToolReference || "value" in reference || reference.receiver !== reference.target) {
|
||||
@@ -2171,9 +2164,12 @@ class Frame<R> {
|
||||
throw typeError(`Cannot assign to read only property '${String(key)}'.`, node)
|
||||
}
|
||||
|
||||
// ToPropertyKey: anything else becomes its string form, so `counts[row.category]` works when the field is null.
|
||||
private toPropertyKey(value: Value): PropertyKey {
|
||||
if (typeof value === "string" || typeof value === "number" || typeof value === "symbol") return value
|
||||
return coerceToString(value)
|
||||
private toPropertyKey(value: Value, node: AstNode): PropertyKey {
|
||||
if (typeof value === "string" || typeof value === "number") {
|
||||
return value
|
||||
}
|
||||
if (value === AsyncIteratorSymbol || value === IteratorSymbol) return value
|
||||
|
||||
throw typeError("Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.", node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ export class Fn extends Callable {
|
||||
name: string,
|
||||
readonly parameters: ReadonlyArray<Pattern>,
|
||||
readonly body: BlockStatement | Expression,
|
||||
readonly capturedScopes: Array<Map<string, Binding>>,
|
||||
readonly capturedScopes: ReadonlyArray<Map<string, Binding>>,
|
||||
readonly async: boolean,
|
||||
readonly generator: boolean,
|
||||
) {
|
||||
@@ -422,12 +422,6 @@ export const coerceToNumber = (value: Value): number => {
|
||||
return value instanceof ToolReference ? Number.NaN : Number(value)
|
||||
}
|
||||
|
||||
/** ToIntegerOrInfinity: NaN is 0, fractions truncate. */
|
||||
export const coerceToInteger = (value: Value): number => {
|
||||
const number = coerceToNumber(value)
|
||||
return Number.isNaN(number) ? 0 : Math.trunc(number)
|
||||
}
|
||||
|
||||
/** Values that cannot cross the data boundary: opaque machinery and host-backed wrappers. */
|
||||
export const isRuntimeReference = (value: Value): boolean =>
|
||||
value instanceof Opaque || value instanceof Wrapper || value instanceof ToolReference
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
GeneratorObj,
|
||||
hostIterator,
|
||||
Obj,
|
||||
PromiseObj,
|
||||
coerceToInteger,
|
||||
coerceToNumber,
|
||||
coerceToString,
|
||||
type Value,
|
||||
@@ -22,11 +20,11 @@ import type { Interpreter } from "../interpreter/interpreter.js"
|
||||
import { compareText } from "../tool-runtime.js"
|
||||
|
||||
const arrayLikeSource = (source: Value): { readonly length: number; readonly source: Obj } => {
|
||||
// JS would treat a promise as an empty array-like; that would hide a missing `await`.
|
||||
if (source instanceof Obj && !(source instanceof PromiseObj)) {
|
||||
const length = Math.max(0, coerceToInteger(get(source, "length")))
|
||||
checkArrayLength(length)
|
||||
return { length, source }
|
||||
if (source instanceof Obj && typeof get(source, "length") === "number") {
|
||||
const length = get(source, "length") as number
|
||||
const normalized = Number.isNaN(length) || length <= 0 ? 0 : Math.trunc(length)
|
||||
checkArrayLength(normalized)
|
||||
return { length: normalized, source }
|
||||
}
|
||||
throw invalidData(`Array.from expects an iterable or array-like value, received ${describeValue(source)}.`)
|
||||
}
|
||||
@@ -124,7 +122,13 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
])
|
||||
|
||||
const self = (thisValue: Value, name: string) => receiver(Arr, thisValue, `Array.prototype.${name}`)
|
||||
const optNumber = (value: Value): number | undefined => (value === undefined ? undefined : coerceToInteger(value))
|
||||
const optNumber = (name: string, value: Value, label: string): number | undefined => {
|
||||
if (value === undefined) return undefined
|
||||
if (typeof value !== "number") {
|
||||
throw typeError(`Array.${name} expects ${label} to be a number.`)
|
||||
}
|
||||
return value
|
||||
}
|
||||
// Callback methods fix the iteration length while reading existing elements live.
|
||||
const iterate = (
|
||||
name: string,
|
||||
@@ -149,9 +153,13 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
"join",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const joined = self(thisValue, "join")
|
||||
.items.map((item) => coerceToString(item ?? ""))
|
||||
.join(args[0] === undefined ? "," : coerceToString(args[0]))
|
||||
const target = self(thisValue, "join").items
|
||||
if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
|
||||
throw typeError("Array.join expects zero arguments or one string separator.")
|
||||
}
|
||||
const joined = target
|
||||
.map((item) => coerceToString(item ?? ""))
|
||||
.join(args.length === 0 ? "," : (args[0] as string))
|
||||
checkStringLength(joined.length)
|
||||
return joined
|
||||
},
|
||||
@@ -168,23 +176,40 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
"includes",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
return self(thisValue, "includes").items.includes(args[0], optNumber(args[1]))
|
||||
const target = self(thisValue, "includes").items
|
||||
if (args.length === 0 || args.length > 2) {
|
||||
throw typeError("Array.includes expects a value and optional start index.")
|
||||
}
|
||||
return target.includes(args[0], optNumber("includes", args[1], "start index"))
|
||||
},
|
||||
],
|
||||
["indexOf", 1, (thisValue, args) => self(thisValue, "indexOf").items.indexOf(args[0], optNumber(args[1]))],
|
||||
[
|
||||
"indexOf",
|
||||
1,
|
||||
(thisValue, args) =>
|
||||
self(thisValue, "indexOf").items.indexOf(args[0], optNumber("indexOf", args[1], "start index")),
|
||||
],
|
||||
[
|
||||
"lastIndexOf",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "lastIndexOf").items
|
||||
return args[1] === undefined ? target.lastIndexOf(args[0]) : target.lastIndexOf(args[0], optNumber(args[1]))
|
||||
return args[1] === undefined
|
||||
? target.lastIndexOf(args[0])
|
||||
: target.lastIndexOf(args[0], optNumber("lastIndexOf", args[1], "start index"))
|
||||
},
|
||||
],
|
||||
["at", 1, (thisValue, args) => self(thisValue, "at").items.at(optNumber(args[0]) ?? 0)],
|
||||
["at", 1, (thisValue, args) => self(thisValue, "at").items.at(optNumber("at", args[0], "index") ?? 0)],
|
||||
[
|
||||
"slice",
|
||||
2,
|
||||
(thisValue, args) => wrap(self(thisValue, "slice").items.slice(optNumber(args[0]), optNumber(args[1]))),
|
||||
(thisValue, args) =>
|
||||
wrap(
|
||||
self(thisValue, "slice").items.slice(
|
||||
optNumber("slice", args[0], "start"),
|
||||
optNumber("slice", args[1], "end"),
|
||||
),
|
||||
),
|
||||
],
|
||||
[
|
||||
"concat",
|
||||
@@ -203,7 +228,7 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
(thisValue, args) => {
|
||||
const flatten = (items: Array<Value>, depth: number): Array<Value> =>
|
||||
items.flatMap((item) => (item instanceof Arr && depth > 0 ? flatten(item.items, depth - 1) : [item]))
|
||||
const flattened = flatten(self(thisValue, "flat").items, optNumber(args[0]) ?? 1)
|
||||
const flattened = flatten(self(thisValue, "flat").items, optNumber("flat", args[0], "depth") ?? 1)
|
||||
checkArrayLength(flattened.length)
|
||||
return wrap(flattened)
|
||||
},
|
||||
@@ -249,7 +274,7 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
2,
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "with").items
|
||||
const index = optNumber(args[0]) ?? 0
|
||||
const index = optNumber("with", args[0], "index") ?? 0
|
||||
const resolved = index < 0 ? target.length + index : index
|
||||
if (resolved < 0 || resolved >= target.length) throw rangeError("Array.with index is out of range.")
|
||||
const copied = [...target]
|
||||
@@ -284,9 +309,9 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "splice")
|
||||
if (args.length === 0) return wrap(target.items.splice(0, 0))
|
||||
const start = optNumber(args[0]) ?? 0
|
||||
const start = optNumber("splice", args[0], "start") ?? 0
|
||||
if (args.length === 1) return wrap(target.items.splice(start))
|
||||
const deleteCount = optNumber(args[1]) ?? 0
|
||||
const deleteCount = optNumber("splice", args[1], "delete count") ?? 0
|
||||
const inserted = args.slice(2)
|
||||
for (const item of inserted) rejectCircularInsertion(target, item, "Array.splice result")
|
||||
return wrap(target.items.splice(start, deleteCount, ...inserted))
|
||||
@@ -298,9 +323,9 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
(thisValue, args) => {
|
||||
const copied = [...self(thisValue, "toSpliced").items]
|
||||
if (args.length === 0) return wrap(copied)
|
||||
const start = optNumber(args[0]) ?? 0
|
||||
const start = optNumber("toSpliced", args[0], "start") ?? 0
|
||||
if (args.length === 1) copied.splice(start)
|
||||
else copied.splice(start, optNumber(args[1]) ?? 0, ...args.slice(2))
|
||||
else copied.splice(start, optNumber("toSpliced", args[1], "delete count") ?? 0, ...args.slice(2))
|
||||
return wrap(copied)
|
||||
},
|
||||
],
|
||||
@@ -310,7 +335,7 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "fill")
|
||||
rejectCircularInsertion(target, args[0], "Array.fill result")
|
||||
target.items.fill(args[0], optNumber(args[1]), optNumber(args[2]))
|
||||
target.items.fill(args[0], optNumber("fill", args[1], "start"), optNumber("fill", args[2], "end"))
|
||||
return target
|
||||
},
|
||||
],
|
||||
@@ -319,7 +344,11 @@ export const arrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
2,
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "copyWithin")
|
||||
target.items.copyWithin(optNumber(args[0]) ?? 0, optNumber(args[1]) ?? 0, optNumber(args[2]))
|
||||
target.items.copyWithin(
|
||||
optNumber("copyWithin", args[0], "target index") ?? 0,
|
||||
optNumber("copyWithin", args[1], "start") ?? 0,
|
||||
optNumber("copyWithin", args[2], "end"),
|
||||
)
|
||||
return target
|
||||
},
|
||||
],
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
Bytes,
|
||||
hostIterator,
|
||||
Obj,
|
||||
coerceToInteger,
|
||||
coerceToNumber,
|
||||
coerceToString,
|
||||
type Value,
|
||||
@@ -76,28 +75,44 @@ export const uint8ArrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
])
|
||||
|
||||
const self = (thisValue: Value, name: string) => receiver(Bytes, thisValue, `Uint8Array.prototype.${name}`)
|
||||
const optNumber = (value: Value): number | undefined => (value === undefined ? undefined : coerceToInteger(value))
|
||||
const optNumber = (name: string, value: Value, label: string): number | undefined => {
|
||||
if (value === undefined) return undefined
|
||||
if (typeof value !== "number") throw typeError(`Uint8Array.${name} expects ${label} to be a number.`)
|
||||
return value
|
||||
}
|
||||
const wrapAll = (items: Array<Value>) => new Arr(builtins.Array, items)
|
||||
defineAccessor(proto, "length", (thisValue) => self(thisValue, "length").bytes.length)
|
||||
methods(builtins, proto, [
|
||||
["at", 1, (thisValue, args) => self(thisValue, "at").bytes.at(optNumber(args[0]) ?? 0)],
|
||||
["at", 1, (thisValue, args) => self(thisValue, "at").bytes.at(optNumber("at", args[0], "index") ?? 0)],
|
||||
[
|
||||
"slice",
|
||||
2,
|
||||
(thisValue, args) => wrap(self(thisValue, "slice").bytes.slice(optNumber(args[0]), optNumber(args[1]))),
|
||||
(thisValue, args) =>
|
||||
wrap(
|
||||
self(thisValue, "slice").bytes.slice(
|
||||
optNumber("slice", args[0], "start"),
|
||||
optNumber("slice", args[1], "end"),
|
||||
),
|
||||
),
|
||||
],
|
||||
// A view on the same bytes, as in JS: writes through one are visible through the other.
|
||||
[
|
||||
"subarray",
|
||||
2,
|
||||
(thisValue, args) => wrap(self(thisValue, "subarray").bytes.subarray(optNumber(args[0]), optNumber(args[1]))),
|
||||
(thisValue, args) =>
|
||||
wrap(
|
||||
self(thisValue, "subarray").bytes.subarray(
|
||||
optNumber("subarray", args[0], "start"),
|
||||
optNumber("subarray", args[1], "end"),
|
||||
),
|
||||
),
|
||||
],
|
||||
[
|
||||
"set",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "set")
|
||||
const offset = optNumber(args[1]) ?? 0
|
||||
const offset = optNumber("set", args[1], "offset") ?? 0
|
||||
return Effect.map(collectBytes(ctx, args[0], "Uint8Array.set"), (source) => {
|
||||
if (!Number.isInteger(offset) || offset < 0 || source.length + offset > target.bytes.length) {
|
||||
throw rangeError("Uint8Array.set: the source does not fit at that offset.")
|
||||
@@ -112,7 +127,11 @@ export const uint8ArrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const target = self(thisValue, "fill")
|
||||
target.bytes.fill(coerceToNumber(args[0]), optNumber(args[1]), optNumber(args[2]))
|
||||
target.bytes.fill(
|
||||
coerceToNumber(args[0]),
|
||||
optNumber("fill", args[1], "start"),
|
||||
optNumber("fill", args[2], "end"),
|
||||
)
|
||||
return target
|
||||
},
|
||||
],
|
||||
@@ -128,7 +147,8 @@ export const uint8ArrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
[
|
||||
"indexOf",
|
||||
1,
|
||||
(thisValue, args) => self(thisValue, "indexOf").bytes.indexOf(coerceToNumber(args[0]), optNumber(args[1])),
|
||||
(thisValue, args) =>
|
||||
self(thisValue, "indexOf").bytes.indexOf(coerceToNumber(args[0]), optNumber("indexOf", args[1], "start index")),
|
||||
],
|
||||
[
|
||||
"lastIndexOf",
|
||||
@@ -137,19 +157,26 @@ export const uint8ArrayGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
const target = self(thisValue, "lastIndexOf").bytes
|
||||
return args[1] === undefined
|
||||
? target.lastIndexOf(coerceToNumber(args[0]))
|
||||
: target.lastIndexOf(coerceToNumber(args[0]), optNumber(args[1]))
|
||||
: target.lastIndexOf(coerceToNumber(args[0]), optNumber("lastIndexOf", args[1], "start index"))
|
||||
},
|
||||
],
|
||||
[
|
||||
"includes",
|
||||
1,
|
||||
(thisValue, args) => self(thisValue, "includes").bytes.includes(coerceToNumber(args[0]), optNumber(args[1])),
|
||||
(thisValue, args) =>
|
||||
self(thisValue, "includes").bytes.includes(
|
||||
coerceToNumber(args[0]),
|
||||
optNumber("includes", args[1], "start index"),
|
||||
),
|
||||
],
|
||||
[
|
||||
"join",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const joined = self(thisValue, "join").bytes.join(args[0] === undefined ? "," : coerceToString(args[0]))
|
||||
if (args.length > 1 || (args.length === 1 && typeof args[0] !== "string")) {
|
||||
throw typeError("Uint8Array.join expects zero arguments or one string separator.")
|
||||
}
|
||||
const joined = self(thisValue, "join").bytes.join(args.length === 0 ? "," : (args[0] as string))
|
||||
checkStringLength(joined.length)
|
||||
return joined
|
||||
},
|
||||
|
||||
@@ -188,7 +188,7 @@ export const mapGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
const target = self(thisValue, "forEach")
|
||||
const apply = applyCollectionCallback(ctx, args[0], "Map.forEach")
|
||||
return Effect.gen(function* () {
|
||||
for (const [key, item] of target.map.entries()) yield* apply([item, key, target])
|
||||
for (const [key, item] of Array.from(target.map.entries())) yield* apply([item, key, target])
|
||||
return undefined
|
||||
})
|
||||
},
|
||||
@@ -386,7 +386,7 @@ export const setGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
const target = self(thisValue, "forEach")
|
||||
const apply = applyCollectionCallback(ctx, args[0], "Set.forEach")
|
||||
return Effect.gen(function* () {
|
||||
for (const item of target.set.values()) yield* apply([item, item, target])
|
||||
for (const item of Array.from(target.set.values())) yield* apply([item, item, target])
|
||||
return undefined
|
||||
})
|
||||
},
|
||||
|
||||
@@ -3,10 +3,10 @@ import { methods } from "../interpreter/native.js"
|
||||
import { applyCollectionCallback } from "../interpreter/callback.js"
|
||||
import type { Interpreter } from "../interpreter/interpreter.js"
|
||||
import { checkStringLength } from "../interpreter/limits.js"
|
||||
import { syntaxError } from "../interpreter/model.js"
|
||||
import { syntaxError, typeError } from "../interpreter/model.js"
|
||||
import { typeofValue } from "../interpreter/references.js"
|
||||
import { fromJson, toJson } from "../data.js"
|
||||
import { get, keys, Arr, Obj, coerceToString, record, remove, set, type Value } from "../interpreter/objects.js"
|
||||
import { get, keys, Arr, Obj, record, remove, set, type Value } from "../interpreter/objects.js"
|
||||
|
||||
export const jsonGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
const json = new Obj(ctx.builtins.Object)
|
||||
@@ -18,7 +18,8 @@ export const jsonGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
}
|
||||
|
||||
const parse = <R>(ctx: Interpreter<R>, args: Array<Value>): Effect.Effect<Value, unknown, R> => {
|
||||
const text = coerceToString(args[0])
|
||||
const text = args[0]
|
||||
if (typeof text !== "string") throw typeError("JSON.parse expects a string.")
|
||||
|
||||
const parsed = (() => {
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Effect } from "effect"
|
||||
import { constants, type Method, methods } from "../interpreter/native.js"
|
||||
import { typeError } from "../interpreter/model.js"
|
||||
import { Obj, coerceToNumber } from "../interpreter/objects.js"
|
||||
import { Obj, type Value } from "../interpreter/objects.js"
|
||||
import { preserveConsumerError } from "../interpreter/callback.js"
|
||||
import type { Interpreter } from "../interpreter/interpreter.js"
|
||||
|
||||
@@ -14,18 +14,31 @@ declare global {
|
||||
|
||||
// Validate only the arguments a method consumes; like JS, extras are ignored
|
||||
// (so built-ins work as callbacks receiving (element, index, array)).
|
||||
const unary = (name: string, op: (a: number) => number): Method => [name, 1, (_, args) => op(coerceToNumber(args[0]))]
|
||||
const number = (name: string, args: Array<Value>, index: number): number => {
|
||||
if (index >= args.length) return Number.NaN
|
||||
const arg = args[index]
|
||||
if (typeof arg !== "number") throw typeError(`Math.${name} expects number arguments.`)
|
||||
return arg
|
||||
}
|
||||
|
||||
const unary = (name: string, op: (a: number) => number): Method => [name, 1, (_, args) => op(number(name, args, 0))]
|
||||
|
||||
const binary = (name: string, op: (a: number, b: number) => number): Method => [
|
||||
name,
|
||||
2,
|
||||
(_, args) => op(coerceToNumber(args[0]), coerceToNumber(args[1])),
|
||||
(_, args) => op(number(name, args, 0), number(name, args, 1)),
|
||||
]
|
||||
|
||||
const variadic = (name: string, op: (...values: Array<number>) => number): Method => [
|
||||
name,
|
||||
2,
|
||||
(_, args) => op(...args.map(coerceToNumber)),
|
||||
(_, args) =>
|
||||
op(
|
||||
...args.map((arg) => {
|
||||
if (typeof arg !== "number") throw typeError(`Math.${name} expects number arguments.`)
|
||||
return arg
|
||||
}),
|
||||
),
|
||||
]
|
||||
|
||||
export const mathGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { constructor, constants, methods } from "../interpreter/native.js"
|
||||
import { coerceToNumber, coerceToString, type Value } from "../interpreter/objects.js"
|
||||
import { coerceToString, type Value } from "../interpreter/objects.js"
|
||||
import { rangeError, typeError } from "../interpreter/model.js"
|
||||
import type { Interpreter } from "../interpreter/interpreter.js"
|
||||
import { coercion } from "./value.js"
|
||||
@@ -30,7 +30,11 @@ export const numberGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
"parseInt",
|
||||
2,
|
||||
(_, args) => {
|
||||
return parseInt(coerceToString(args[0]), coerceToNumber(args[1]))
|
||||
const radix = args[1]
|
||||
if (radix !== undefined && typeof radix !== "number") {
|
||||
throw typeError("Number.parseInt expects a numeric radix.")
|
||||
}
|
||||
return parseInt(coerceToString(args[0]), radix)
|
||||
},
|
||||
],
|
||||
["parseFloat", 1, (_, args) => parseFloat(coerceToString(args[0]))],
|
||||
@@ -40,17 +44,25 @@ export const numberGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
if (typeof thisValue === "number") return thisValue
|
||||
throw typeError(`Number.prototype.${name} requires that 'this' be a Number.`)
|
||||
}
|
||||
const optNum = (arg: Value): number | undefined => (arg === undefined ? undefined : coerceToNumber(arg))
|
||||
const optNum = (name: string, arg: Value): number | undefined => {
|
||||
if (arg === undefined) return undefined
|
||||
if (typeof arg !== "number") throw typeError(`Number.${name} expects a number argument.`)
|
||||
return arg
|
||||
}
|
||||
methods(builtins, builtins.Number, [
|
||||
["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum(args[0]))],
|
||||
["toFixed", 1, (thisValue, args) => self(thisValue, "toFixed").toFixed(optNum("toFixed", args[0]))],
|
||||
["toLocaleString", 0, (thisValue) => self(thisValue, "toLocaleString").toLocaleString("en-US")],
|
||||
["toExponential", 1, (thisValue, args) => self(thisValue, "toExponential").toExponential(optNum(args[0]))],
|
||||
[
|
||||
"toExponential",
|
||||
1,
|
||||
(thisValue, args) => self(thisValue, "toExponential").toExponential(optNum("toExponential", args[0])),
|
||||
],
|
||||
[
|
||||
"toPrecision",
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const value = self(thisValue, "toPrecision")
|
||||
const digits = optNum(args[0])
|
||||
const digits = optNum("toPrecision", args[0])
|
||||
return digits === undefined ? value.toString() : value.toPrecision(digits)
|
||||
},
|
||||
],
|
||||
@@ -59,7 +71,7 @@ export const numberGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
1,
|
||||
(thisValue, args) => {
|
||||
const value = self(thisValue, "toString")
|
||||
const radix = optNum(args[0])
|
||||
const radix = optNum("toString", args[0])
|
||||
if (radix !== undefined && (radix < 2 || radix > 36)) {
|
||||
throw rangeError("Number.toString radix must be between 2 and 36.")
|
||||
}
|
||||
|
||||
@@ -93,7 +93,15 @@ export const stringGlobal = <R>(ctx: Interpreter<R>) => {
|
||||
const codeUnits = (name: string, op: (...codes: Array<number>) => string): Method => [
|
||||
name,
|
||||
1,
|
||||
(_, args) => op(...args.map(coerceToNumber)),
|
||||
(_, args) =>
|
||||
op(
|
||||
...args.map((arg) => {
|
||||
if (typeof arg !== "number") {
|
||||
throw typeError(`String.${name} expects number arguments.`)
|
||||
}
|
||||
return arg
|
||||
}),
|
||||
),
|
||||
]
|
||||
methods(builtins, string, [
|
||||
codeUnits("fromCharCode", String.fromCharCode),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fn } from "../interpreter/native.js"
|
||||
import { typeError } from "../interpreter/model.js"
|
||||
import { coerceToNumber, coerceToString, type Native, type Value } from "../interpreter/objects.js"
|
||||
import type { Interpreter } from "../interpreter/interpreter.js"
|
||||
|
||||
@@ -19,7 +20,11 @@ const coerce = <R>(ctx: Interpreter<R>, name: Coercion, args: Array<Value>): Val
|
||||
if (name === "isFinite") return Number.isFinite(coerceToNumber(raw))
|
||||
if (name === "isNaN") return Number.isNaN(coerceToNumber(raw))
|
||||
if (name === "parseInt") {
|
||||
return parseInt(coerceToString(raw), coerceToNumber(args[1]))
|
||||
const radix = args[1]
|
||||
if (radix !== undefined && typeof radix !== "number") {
|
||||
throw typeError("parseInt expects a numeric radix.")
|
||||
}
|
||||
return parseInt(coerceToString(raw), radix)
|
||||
}
|
||||
if (name === "parseFloat") return parseFloat(coerceToString(raw))
|
||||
return coerceToString(raw)
|
||||
|
||||
@@ -176,7 +176,7 @@ const describeTool = <R>(visible: VisibleTool<R>): ToolDescription => {
|
||||
// Joining the final fragments avoids retaining the rendering's intermediate string ropes in JSC.
|
||||
return (signature ??= [
|
||||
toolExpression(visible.path),
|
||||
isEmptyInput(visible.tool) ? "()" : `(${inputTypeScript(visible.tool, true)})`,
|
||||
isEmptyInput(visible.tool) ? "()" : `(input: ${inputTypeScript(visible.tool, true)})`,
|
||||
`: Promise<${outputTypeScript(visible.tool, true)}>`,
|
||||
].join(""))
|
||||
},
|
||||
@@ -211,32 +211,6 @@ const termForms = (term: string): Array<string> => {
|
||||
return forms
|
||||
}
|
||||
|
||||
const rank = (entries: ReadonlyArray<SearchEntry>, query: string): Array<SearchEntry> => {
|
||||
const terms = tokenize(query).map(termForms)
|
||||
return entries
|
||||
.map((entry) => {
|
||||
const path = entry.description.path.toLowerCase()
|
||||
const description = entry.description.description.toLowerCase()
|
||||
const score = terms.reduce(
|
||||
(total, forms) =>
|
||||
total +
|
||||
(forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
|
||||
(forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
|
||||
(forms.some((form) => path.includes(form)) ? 8 : 0) +
|
||||
(forms.some((form) => description.includes(form)) ? 4 : 0) +
|
||||
(forms.some((form) => entry.searchText.includes(form)) ? 2 : 0),
|
||||
0,
|
||||
)
|
||||
return { entry, score }
|
||||
})
|
||||
.filter(({ score }) => terms.length === 0 || score > 0)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
right.score - left.score || compareText(left.entry.description.path, right.entry.description.path),
|
||||
)
|
||||
.map(({ entry }) => entry)
|
||||
}
|
||||
|
||||
const makeSearchTool = (searchIndex: ReadonlyArray<SearchEntry>): Tool => ({
|
||||
_tag: "CodeModeTool",
|
||||
description: "Search available tools",
|
||||
@@ -247,15 +221,14 @@ const makeSearchTool = (searchIndex: ReadonlyArray<SearchEntry>): Tool => ({
|
||||
const request = input as typeof SearchInput.Type
|
||||
const query = request.query ?? ""
|
||||
const offset = request.offset ?? 0
|
||||
let ns = request.namespace
|
||||
if (ns !== undefined && !searchIndex.some((entry) => entry.description.path.startsWith("tools."))) {
|
||||
if (ns === "tools") ns = undefined
|
||||
else if (ns.startsWith("tools.")) ns = ns.slice("tools.".length)
|
||||
}
|
||||
const scoped =
|
||||
ns === undefined
|
||||
request.namespace === undefined
|
||||
? searchIndex
|
||||
: searchIndex.filter((entry) => entry.description.path === ns || entry.description.path.startsWith(`${ns}.`))
|
||||
: searchIndex.filter(
|
||||
(entry) =>
|
||||
entry.description.path === request.namespace ||
|
||||
entry.description.path.startsWith(`${request.namespace}.`),
|
||||
)
|
||||
const trimmed = query.trim()
|
||||
const pathQuery = trimmed.startsWith("tools.") ? trimmed.slice("tools.".length) : trimmed
|
||||
const exact =
|
||||
@@ -264,7 +237,32 @@ const makeSearchTool = (searchIndex: ReadonlyArray<SearchEntry>): Tool => ({
|
||||
: scoped.find(
|
||||
(entry) => entry.description.path === pathQuery || toolExpression(entry.description.path) === trimmed,
|
||||
)
|
||||
const ranked = exact !== undefined ? [exact] : rank(scoped, query)
|
||||
const terms = tokenize(query).map(termForms)
|
||||
const ranked =
|
||||
exact !== undefined
|
||||
? [exact]
|
||||
: scoped
|
||||
.map((entry) => {
|
||||
const path = entry.description.path.toLowerCase()
|
||||
const description = entry.description.description.toLowerCase()
|
||||
const score = terms.reduce(
|
||||
(total, forms) =>
|
||||
total +
|
||||
(forms.some((form) => path === form || path.endsWith(`.${form}`)) ? 20 : 0) +
|
||||
(forms.some((form) => entry.pathWords.includes(form)) ? 12 : 0) +
|
||||
(forms.some((form) => path.includes(form)) ? 8 : 0) +
|
||||
(forms.some((form) => description.includes(form)) ? 4 : 0) +
|
||||
(forms.some((form) => entry.searchText.includes(form)) ? 2 : 0),
|
||||
0,
|
||||
)
|
||||
return { entry, score }
|
||||
})
|
||||
.filter(({ score }) => terms.length === 0 || score > 0)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
right.score - left.score || compareText(left.entry.description.path, right.entry.description.path),
|
||||
)
|
||||
.map(({ entry }) => entry)
|
||||
const items = ranked.slice(offset, offset + (request.limit ?? defaultSearchLimit)).map(({ description }) => ({
|
||||
...description,
|
||||
path: toolExpression(description.path),
|
||||
@@ -281,7 +279,7 @@ const makeSearchTool = (searchIndex: ReadonlyArray<SearchEntry>): Tool => ({
|
||||
/** Exact callable signature of the built-in `search` function, for host-owned instructions. */
|
||||
export const searchSignature = (() => {
|
||||
const tool = makeSearchTool([])
|
||||
return `search(${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`
|
||||
return `search(input: ${inputTypeScript(tool, true)}): ${outputTypeScript(tool, true)}`
|
||||
})()
|
||||
|
||||
const toSearchEntry = <R>(visible: VisibleTool<R>): SearchEntry => ({
|
||||
@@ -330,23 +328,13 @@ const namespaceKeys = <R>(root: ToolNode<R>, path: ReadonlyArray<string>): Reado
|
||||
return Array.from(node.children.keys())
|
||||
}
|
||||
|
||||
const resolve = <R>(root: ToolNode<R>, path: ReadonlyArray<string>, index: ReadonlyArray<SearchEntry>): Tool<R> => {
|
||||
const resolve = <R>(root: ToolNode<R>, path: ReadonlyArray<string>): Tool<R> => {
|
||||
const segments = canonicalSegments(path)
|
||||
const node = lookup(root, segments)
|
||||
if (node === undefined) {
|
||||
const name = segments.join(".")
|
||||
const ns = segments.length > 1 && root.children.has(segments[0]) ? segments[0] : undefined
|
||||
const closest = rank(
|
||||
ns ? index.filter((entry) => entry.description.path.startsWith(`${ns}.`)) : index,
|
||||
ns ? segments.slice(1).join(" ") : name,
|
||||
)[0]
|
||||
throw new ToolRuntimeError(
|
||||
"UnknownTool",
|
||||
closest
|
||||
? `Unknown tool '${name}'. Did you mean ${toolExpression(closest.description.path)}?`
|
||||
: `Unknown tool '${name}'.`,
|
||||
["Use search to find available tools."],
|
||||
)
|
||||
throw new ToolRuntimeError("UnknownTool", `Unknown tool '${segments.join(".")}'.`, [
|
||||
"The tool may have been removed or renamed. Use search to find available tools.",
|
||||
])
|
||||
}
|
||||
if (node.tool === undefined) {
|
||||
throw new ToolRuntimeError("UnknownTool", `Tool '${segments.join(".")}' is not callable.`)
|
||||
@@ -451,7 +439,7 @@ export const make = <R>(
|
||||
// Models often write `tools.search(...)` for the bare `search(...)`; honor it unless a tool owns that path.
|
||||
if (segments.length === 1 && segments[0] === "search" && lookup(root, segments) === undefined)
|
||||
return executeTool("search", makeSearchTool(prepared.searchIndex), args)
|
||||
return executeTool(segments.join("."), resolve(root, path, prepared.searchIndex), args)
|
||||
return executeTool(segments.join("."), resolve(root, path), args)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,8 +271,10 @@ describe("still-rejected callables get the wrap hint", () => {
|
||||
test("built-in references work as replacers", async () => {
|
||||
// Like real JS: JSON.stringify(match, offset, string) quotes the match.
|
||||
expect(await value(`return "abc".replace(/b/, JSON.stringify)`)).toBe('a"b"c')
|
||||
// Math methods coerce the match string, as in JS.
|
||||
expect(await value(`return "3.7".replace(/\\d\\.\\d/, Math.floor)`)).toBe("3")
|
||||
// Math methods stay strict about consumed arguments: a match string is not coerced.
|
||||
expect((await error(`return "3.7".replace(/\\d\\.\\d/, Math.floor)`)).message).toContain(
|
||||
"Math.floor expects number arguments",
|
||||
)
|
||||
})
|
||||
|
||||
test("non-callables still get the plain callback error", async () => {
|
||||
|
||||
@@ -581,7 +581,7 @@ describe("CodeMode schema flexibility", () => {
|
||||
{
|
||||
path: "adapter.call",
|
||||
description: "Call an adapter-described tool",
|
||||
signature: "tools.adapter.call({\n id: string,\n count?: number,\n}): Promise<void>",
|
||||
signature: "tools.adapter.call(input: {\n id: string,\n count?: number,\n}): Promise<void>",
|
||||
},
|
||||
])
|
||||
|
||||
@@ -664,7 +664,7 @@ describe("CodeMode schema flexibility", () => {
|
||||
{
|
||||
path: "users.lookup",
|
||||
description: "Look up a user",
|
||||
signature: "tools.users.lookup({\n login: string,\n}): Promise<{\n login: string,\n id: number,\n}>",
|
||||
signature: "tools.users.lookup(input: {\n login: string,\n}): Promise<{\n login: string,\n id: number,\n}>",
|
||||
},
|
||||
])
|
||||
|
||||
@@ -680,7 +680,7 @@ describe("CodeMode schema flexibility", () => {
|
||||
execute: () => Effect.succeed("pong"),
|
||||
})
|
||||
const runtime = CodeMode.make({ tools: { net: { ping } } })
|
||||
expect(runtime.catalog[0]?.signature).toBe("tools.net.ping({\n host: string,\n}): Promise<void>")
|
||||
expect(runtime.catalog[0]?.signature).toBe("tools.net.ping(input: {\n host: string,\n}): Promise<void>")
|
||||
|
||||
const result = await Effect.runPromise(runtime.execute(`return await tools.net.ping({ host: "example.test" })`))
|
||||
expect(result.ok).toBe(true)
|
||||
@@ -737,7 +737,7 @@ describe("CodeMode public contract", () => {
|
||||
{
|
||||
path: "orders.lookup",
|
||||
description: "Look up an order by ID",
|
||||
signature: "tools.orders.lookup({\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
|
||||
signature: "tools.orders.lookup(input: {\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
|
||||
},
|
||||
])
|
||||
|
||||
@@ -749,7 +749,8 @@ describe("CodeMode public contract", () => {
|
||||
{
|
||||
path: "tools.orders.lookup",
|
||||
description: "Look up an order by ID",
|
||||
signature: "tools.orders.lookup({\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
|
||||
signature:
|
||||
"tools.orders.lookup(input: {\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
|
||||
},
|
||||
],
|
||||
remaining: 0,
|
||||
@@ -791,7 +792,7 @@ describe("CodeMode public contract", () => {
|
||||
{
|
||||
path: "context7.resolve-library-id",
|
||||
description: "Resolve a library ID",
|
||||
signature: 'tools.context7["resolve-library-id"]({\n libraryName: string,\n}): Promise<string>',
|
||||
signature: 'tools.context7["resolve-library-id"](input: {\n libraryName: string,\n}): Promise<string>',
|
||||
},
|
||||
])
|
||||
|
||||
@@ -803,7 +804,7 @@ describe("CodeMode public contract", () => {
|
||||
{
|
||||
path: 'tools.context7["resolve-library-id"]',
|
||||
description: "Resolve a library ID",
|
||||
signature: 'tools.context7["resolve-library-id"]({\n libraryName: string,\n}): Promise<string>',
|
||||
signature: 'tools.context7["resolve-library-id"](input: {\n libraryName: string,\n}): Promise<string>',
|
||||
},
|
||||
],
|
||||
remaining: 0,
|
||||
@@ -856,12 +857,12 @@ describe("CodeMode public contract", () => {
|
||||
{
|
||||
path: "tools.thread.uploadFile",
|
||||
description: "Upload one readable local file to the current Discord thread",
|
||||
signature: "tools.thread.uploadFile({\n path: string,\n}): Promise<{\n sent: boolean,\n}>",
|
||||
signature: "tools.thread.uploadFile(input: {\n path: string,\n}): Promise<{\n sent: boolean,\n}>",
|
||||
},
|
||||
{
|
||||
path: "tools.thread.generateImage",
|
||||
description: "Generate an image and upload it to the current Discord thread",
|
||||
signature: "tools.thread.generateImage({\n prompt: string,\n}): Promise<{\n sent: boolean,\n}>",
|
||||
signature: "tools.thread.generateImage(input: {\n prompt: string,\n}): Promise<{\n sent: boolean,\n}>",
|
||||
},
|
||||
],
|
||||
remaining: 0,
|
||||
@@ -949,7 +950,7 @@ describe("CodeMode public contract", () => {
|
||||
{
|
||||
path: "tools.many.tool13",
|
||||
description: "Numbered tool 13",
|
||||
signature: "tools.many.tool13({\n id: string,\n}): Promise<string>",
|
||||
signature: "tools.many.tool13(input: {\n id: string,\n}): Promise<string>",
|
||||
},
|
||||
],
|
||||
remaining: 0,
|
||||
@@ -995,10 +996,6 @@ describe("CodeMode public contract", () => {
|
||||
expect(value.items[0]?.path).toBe("tools.linear.list_issues")
|
||||
}
|
||||
|
||||
const prefixed = await Effect.runPromise(runtime.execute(`return search({ query: "", namespace: "tools.github" })`))
|
||||
expect(prefixed.ok).toBe(true)
|
||||
if (prefixed.ok) expect((prefixed.value as { items: Array<unknown> }).items).toHaveLength(2)
|
||||
|
||||
const invalid = await Effect.runPromise(runtime.execute(`return search({ query: "issues", namespace: 7 })`))
|
||||
expect(invalid.ok).toBe(false)
|
||||
if (!invalid.ok) expect(invalid.error.kind).toBe("InvalidToolInput")
|
||||
|
||||
@@ -23,27 +23,6 @@ const value = async (code: string) => {
|
||||
}
|
||||
|
||||
describe("confined generators", () => {
|
||||
test("parameters are bound at the call; only the body waits for next()", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const log = []
|
||||
function* g(a = log.push("param")) { log.push("body"); yield a }
|
||||
const it = g()
|
||||
log.push("created")
|
||||
it.next()
|
||||
function* bad({ x } = null) { yield x }
|
||||
async function* asyncBad({ x } = null) { yield x }
|
||||
let failures = []
|
||||
try { bad() } catch (error) { failures.push(error.constructor.name) }
|
||||
try { asyncBad() } catch (error) { failures.push(error.constructor.name) }
|
||||
return [log, failures]
|
||||
`),
|
||||
).toEqual([
|
||||
["param", "created", "body"],
|
||||
["TypeError", "TypeError"],
|
||||
])
|
||||
})
|
||||
|
||||
// test/built-ins/GeneratorPrototype/next/return-yield-expr.js
|
||||
test("is lazy and preserves next(value), nested suspension, return, and exhaustion", async () => {
|
||||
expect(
|
||||
|
||||
@@ -218,19 +218,6 @@ describe("property deletion", () => {
|
||||
).toEqual([true, true, { keep: 1 }])
|
||||
})
|
||||
|
||||
test("a non-reference operand is evaluated and the result is true; a variable cannot be deleted", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
let called = false
|
||||
const results = [delete 0, delete null, delete { x: 1 }, delete void 0, delete (() => { called = true })()]
|
||||
let variable = 1
|
||||
let failure
|
||||
try { delete variable } catch (error) { failure = error.constructor.name }
|
||||
return [results, called, failure]
|
||||
`),
|
||||
).toEqual([[true, true, true, true, true], true, "TypeError"])
|
||||
})
|
||||
|
||||
test("evaluates computed object and key expressions once", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
@@ -784,17 +771,9 @@ describe("destructuring assignment", () => {
|
||||
).toEqual({ declared: "a", declarationRest: { 1: "b" }, assigned: "c", assignmentRest: { 1: "d" } })
|
||||
})
|
||||
|
||||
test("computed keys of any type become their string form, as in JS", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const counts = {}
|
||||
for (const category of ["a", null, undefined, "a", true, 1.5]) counts[category] = (counts[category] ?? 0) + 1
|
||||
const key = {}
|
||||
const { [key]: value } = { "[object Object]": 7 }
|
||||
const o = { null: 1, "1,2": 2 }
|
||||
return [counts, value, o[null], o[[1, 2]], undefined in o]
|
||||
`),
|
||||
).toEqual([{ a: 2, null: 1, undefined: 1, true: 1, "1.5": 1 }, 7, 1, 2, false])
|
||||
test("rejects computed keys that are not confined property keys", async () => {
|
||||
const err = await error(`const key = {}; const { [key]: value } = {}`)
|
||||
expect(err.message).toContain("Property key must be a string or number")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -839,36 +818,6 @@ describe("coercion parity: global isFinite and isNaN", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("coercion parity: built-in arguments coerce as in JS", () => {
|
||||
test("numeric arguments apply ToIntegerOrInfinity", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
return [
|
||||
[1, 2, 3].indexOf(2, "1"), [1, 2, 3].lastIndexOf(3, "5"), [1, 2, 3].includes(1, "1"),
|
||||
[1, 2, 3, 4].slice("1", "3"), [1, 2, 3].at(null), [1, 2, 3].at(1.7),
|
||||
[1, [2, [3]]].flat(1.9), [1, 2, 3].with(1.5, 9), [1, 2, 3, 4].splice("1", "2"),
|
||||
Math.max("3", "2"), Math.floor(null), Math.hypot("3", "4"),
|
||||
parseInt("11", "2"), Number.parseInt("ff", "16"), (1.5).toFixed("2"), (255).toString("16"),
|
||||
String.fromCharCode("65", 66.9), new Uint8Array([1, 2, 3]).indexOf(2, "1"),
|
||||
]
|
||||
`),
|
||||
).toEqual([1, 2, false, [2, 3], 1, 2, [1, 2, [3]], [1, 9, 3], [2, 3], 3, 0, 5, 3, 255, "1.50", "ff", "AB", 1])
|
||||
})
|
||||
|
||||
test("join separators, JSON.parse text, and Array.from length coerce", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
return [
|
||||
[1, 2].join(null), [1, 2].join(0), [1, 2].join(undefined), new Uint8Array([1, 2]).join(null),
|
||||
JSON.parse(123), JSON.parse(true),
|
||||
Array.from({ length: "2" }), Array.from({ length: 2.5 }), Array.from({ length: -1 }), Array.from({}),
|
||||
]
|
||||
`),
|
||||
).toEqual(["1null2", "102", "1,2", "1null2", 123, true, [null, null], [null, null], [], []])
|
||||
expect((await error(`return JSON.parse(undefined)`)).message).toContain("JSON")
|
||||
})
|
||||
})
|
||||
|
||||
describe("coercion parity: arrays coerce to numbers through their string form", () => {
|
||||
test("arrays with objects become NaN instead of crashing on host ToPrimitive", async () => {
|
||||
expect(await value(`let x = [{}]; x++; return Number.isNaN(x)`)).toBe(true)
|
||||
|
||||
@@ -739,7 +739,7 @@ describe("JSDoc signatures in catalogs and search results", () => {
|
||||
" },",
|
||||
"}",
|
||||
].join("\n")
|
||||
const signature = `tools.constrained(${type}): Promise<${type}>`
|
||||
const signature = `tools.constrained(input: ${type}): Promise<${type}>`
|
||||
expect(runtime.catalog[0]?.signature).toBe(signature)
|
||||
const result = await Effect.runPromise(runtime.execute('return search({ query: "tools.constrained" })'))
|
||||
expect(result.ok).toBe(true)
|
||||
@@ -761,7 +761,7 @@ describe("JSDoc signatures in catalogs and search results", () => {
|
||||
const item = items.find(({ path }) => path === "tools.github.list_issues")!
|
||||
expect(item.signature).toBe(
|
||||
[
|
||||
"tools.github.list_issues({",
|
||||
"tools.github.list_issues(input: {",
|
||||
" /** Repository owner */",
|
||||
" owner: string,",
|
||||
" /** Cursor from the previous response's pageInfo */",
|
||||
@@ -782,7 +782,7 @@ describe("JSDoc signatures in catalogs and search results", () => {
|
||||
const item = items.find(({ path }) => path === "tools.orders.lookup")!
|
||||
expect(item.signature).toBe(
|
||||
[
|
||||
"tools.orders.lookup({",
|
||||
"tools.orders.lookup(input: {",
|
||||
" /** Order identifier */",
|
||||
" id: string,",
|
||||
" verbose?: boolean,",
|
||||
@@ -825,7 +825,7 @@ describe("non-identifier tool paths", () => {
|
||||
|
||||
test("catalog signatures use bracket notation for dashed tool names", () => {
|
||||
expect(runtime.catalog[0]?.signature).toBe(
|
||||
'tools.context7["resolve-library-id"]({\n query: string,\n libraryName: string,\n}): Promise<unknown>',
|
||||
'tools.context7["resolve-library-id"](input: {\n query: string,\n libraryName: string,\n}): Promise<unknown>',
|
||||
)
|
||||
})
|
||||
|
||||
@@ -836,6 +836,6 @@ describe("non-identifier tool paths", () => {
|
||||
|
||||
const value = result.value as { items: Array<{ path: string; signature: string }> }
|
||||
expect(value.items[0]?.path).toBe('tools.context7["resolve-library-id"]')
|
||||
expect(value.items[0]?.signature).toContain('tools.context7["resolve-library-id"]({')
|
||||
expect(value.items[0]?.signature).toContain('tools.context7["resolve-library-id"](input: {')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -947,19 +947,6 @@ describe("Map", () => {
|
||||
})
|
||||
|
||||
describe("Set", () => {
|
||||
test("forEach is live on Map and Set: deleted entries are skipped and added ones visited", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const m = new Map([[1, "a"], [2, "b"]])
|
||||
const s = new Set([1, 2])
|
||||
const seen = []
|
||||
m.forEach((v, k) => { seen.push(k); if (k === 1) { m.delete(2); m.set(3, "c") } })
|
||||
s.forEach((v) => { seen.push(v); if (v === 1) { s.delete(2); s.add(3) } })
|
||||
return seen
|
||||
`),
|
||||
).toEqual([1, 3, 1, 3])
|
||||
})
|
||||
|
||||
test("add/has/delete/size with chaining", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
built-ins/Array/prototype/at/index-argument-tointeger.js # Array.at expects index to be a number.
|
||||
built-ins/Array/prototype/at/index-non-numeric-argument-tointeger.js # Array.at expects index to be a number.
|
||||
built-ins/Array/prototype/concat/S15.4.4.4_A2_T1.js # Array.prototype.concat called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/concat/S15.4.4.4_A2_T2.js # Array.prototype.concat called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/concat/S15.4.4.4_A3_T1.js # arr.hasOwnProperty("1") must return true Expected SameValue(«false», «true») to be true
|
||||
built-ins/Array/prototype/concat/S15.4.4.4_A3_T2.js # b.hasOwnProperty("2") must return true Expected SameValue(«false», «true») to be true
|
||||
built-ins/Array/prototype/concat/S15.4.4.4_A3_T3.js # b.hasOwnProperty("2") must return true Expected SameValue(«false», «true») to be true
|
||||
built-ins/Array/prototype/concat/create-ctor-non-object.js # a.concat() throws a TypeError exception Expected a TypeError to be thrown but no exception was throw
|
||||
built-ins/Array/prototype/copyWithin/coerced-values-end.js # Array.copyWithin expects end to be a number.
|
||||
built-ins/Array/prototype/copyWithin/coerced-values-start-change-target.js # Array.copyWithin expects start to be a number.
|
||||
built-ins/Array/prototype/copyWithin/coerced-values-start.js # Array.copyWithin expects start to be a number.
|
||||
built-ins/Array/prototype/copyWithin/coerced-values-target.js # Array.copyWithin expects target index to be a number.
|
||||
built-ins/Array/prototype/copyWithin/return-abrupt-from-end.js # Expected a Test262Error but got a TypeError
|
||||
built-ins/Array/prototype/copyWithin/return-abrupt-from-start.js # Expected a Test262Error but got a TypeError
|
||||
built-ins/Array/prototype/copyWithin/return-abrupt-from-target.js # Expected a Test262Error but got a TypeError
|
||||
built-ins/Array/prototype/every/15.4.4.16-7-6.js # res Expected SameValue(«true», «false») to be true
|
||||
built-ins/Array/prototype/every/15.4.4.16-7-c-i-8.js # [, , , ].every(callbackfn) Expected SameValue(«true», «false») to be true
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-10.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-13.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
@@ -19,9 +24,11 @@ built-ins/Array/prototype/every/15.4.4.16-8-5.js # … cannot be constructed: u
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-6.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-7.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/every/15.4.4.16-8-8.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/fill/coerced-indexes.js # Array.fill expects start to be a number.
|
||||
built-ins/Array/prototype/fill/return-abrupt-from-end.js # Expected a Test262Error but got a TypeError
|
||||
built-ins/Array/prototype/fill/return-abrupt-from-start.js # Expected a Test262Error but got a TypeError
|
||||
built-ins/Array/prototype/filter/15.4.4.20-10-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/filter/15.4.4.20-10-4.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/filter/15.4.4.20-6-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/filter/15.4.4.20-6-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/filter/15.4.4.20-6-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
@@ -32,10 +39,12 @@ built-ins/Array/prototype/filter/15.4.4.20-6-8.js # … cannot be constructed:
|
||||
built-ins/Array/prototype/filter/15.4.4.20-9-6.js # resArr.length Expected SameValue(«3», «4») to be true
|
||||
built-ins/Array/prototype/filter/15.4.4.20-9-c-i-8.js # newArr.length Expected SameValue(«0», «1») to be true
|
||||
built-ins/Array/prototype/filter/create-ctor-non-object.js # null value Expected a TypeError to be thrown but no exception was thrown at all
|
||||
built-ins/Array/prototype/flat/non-numeric-depth-should-not-throw.js # Array.flat expects depth to be a number.
|
||||
built-ins/Array/prototype/flat/non-object-ctor-throws.js # null value Expected a TypeError to be thrown but no exception was thrown at all
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-7-5.js # callCnt Expected SameValue(«4», «5») to be true
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-7-c-i-8.js # testResult !== true
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-10.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-12.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
@@ -44,33 +53,61 @@ built-ins/Array/prototype/forEach/15.4.4.18-8-6.js # … cannot be constructed:
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-7.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-8.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/forEach/15.4.4.18-8-9.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/includes/length-zero-returns-false.js # Array.includes expects a value and optional start index.
|
||||
built-ins/Array/prototype/includes/no-arg.js # Array.includes expects a value and optional start index.
|
||||
built-ins/Array/prototype/includes/return-abrupt-tointeger-fromindex.js # Expected a Test262Error but got a TypeError
|
||||
built-ins/Array/prototype/includes/tointeger-fromindex.js # Array.includes expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-1.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-15.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-16.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-17.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-18.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-19.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-20.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-21.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-22.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-23.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-24.js # toStringAccessed
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-25.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-3.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-5-5.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-9-7.js # Array assignment result contains a circular value.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-9-a-3.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-9-a-6.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/indexOf/15.4.4.14-9-b-i-7.js # [, , , ].indexOf(true) Expected SameValue(«-1», «0») to be true
|
||||
built-ins/Array/prototype/indexOf/length-zero-returns-minus-one.js # Array.indexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A1.2_T2.js # Array.join expects zero arguments or one string separator.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A2_T1.js # Array.prototype.join called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A2_T4.js # Array.prototype.join called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A3.1_T1.js # Array.join expects zero arguments or one string separator.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A3.1_T2.js # Array.join expects zero arguments or one string separator.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A3.2_T2.js # x.join() must return "*" Expected SameValue(«"[object Object]"», «"*"») to be true
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A4_T3.js # Array.prototype.join called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/join/S15.4.4.5_A5_T1.js # #1: Array.prototype[1] = 1; x = [0]; x.length = 2; x.join() === "0,1". Actual: 0,
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-1.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-15.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-16.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-17.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-18.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-19.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-20.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-21.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-22.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-23.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-24.js # toStringAccessed
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-25.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-3.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-4.js # a.lastIndexOf(2,undefined) Expected SameValue(«1», «-1») to be true
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-5-5.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-8-7.js # Array assignment result contains a circular value.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-8-a-3.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-8-a-6.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/lastIndexOf/15.4.4.15-8-b-i-7.js # [, , , ].lastIndexOf(true) Expected SameValue(«-1», «0») to be true
|
||||
built-ins/Array/prototype/lastIndexOf/length-zero-returns-minus-one.js # Array.lastIndexOf expects start index to be a number.
|
||||
built-ins/Array/prototype/map/15.4.4.19-8-6.js # resArr[4] Expected SameValue(«undefined», «1») to be true
|
||||
built-ins/Array/prototype/map/15.4.4.19-8-c-i-8.js # newArr[1] Expected SameValue(«13», «true») to be true
|
||||
built-ins/Array/prototype/map/15.4.4.19-9-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/map/15.4.4.19-9-4.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/map/create-ctor-non-object.js # null value Expected a TypeError to be thrown but no exception was thrown at all
|
||||
built-ins/Array/prototype/pop/S15.4.4.6_A2_T1.js # Array.prototype.pop called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/pop/S15.4.4.6_A2_T4.js # Array.prototype.pop called on incompatible receiver a data object.
|
||||
@@ -90,6 +127,7 @@ built-ins/Array/prototype/reduce/15.4.4.21-10-3.js # … cannot be constructed:
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-10-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-10-6.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-10-7.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-10-8.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-5-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-5-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduce/15.4.4.21-5-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
@@ -113,6 +151,7 @@ built-ins/Array/prototype/reduceRight/15.4.4.22-10-3.js # … cannot be constru
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-10-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-10-6.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-10-7.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-10-8.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-5-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-5-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/reduceRight/15.4.4.22-5-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
@@ -142,6 +181,7 @@ built-ins/Array/prototype/shift/S15.4.4.9_A2_T5.js # Array.prototype.shift call
|
||||
built-ins/Array/prototype/shift/S15.4.4.9_A3_T3.js # Array.prototype.shift called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/shift/S15.4.4.9_A4_T1.js # #2: Array.prototype[1] = 1; x = [0]; x.length = 2; x.shift(); x[0] === 1. Actual: undefined
|
||||
built-ins/Array/prototype/shift/S15.4.4.9_A4_T2.js # Array.prototype.shift called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/slice/S15.4.4.10_A2.1_T5.js # Array.slice expects start to be a number.
|
||||
built-ins/Array/prototype/slice/S15.4.4.10_A2.2_T5.js # Array.slice expects end to be a number.
|
||||
built-ins/Array/prototype/slice/S15.4.4.10_A2_T1.js # Array.prototype.slice called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/slice/S15.4.4.10_A2_T2.js # Array.prototype.slice called on incompatible receiver a data object.
|
||||
@@ -157,6 +197,7 @@ built-ins/Array/prototype/slice/create-ctor-non-object.js # null value Expected
|
||||
built-ins/Array/prototype/some/15.4.4.17-7-6.js # res Expected SameValue(«false», «true») to be true
|
||||
built-ins/Array/prototype/some/15.4.4.17-7-c-i-8.js # [, ].some(callbackfn) !== true
|
||||
built-ins/Array/prototype/some/15.4.4.17-8-10.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/some/15.4.4.17-8-13.js # Property key must be a string or number, or Symbol.asyncIterator/Symbol.iterator.
|
||||
built-ins/Array/prototype/some/15.4.4.17-8-2.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/some/15.4.4.17-8-3.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
built-ins/Array/prototype/some/15.4.4.17-8-4.js # … cannot be constructed: user-defined constructors and classes are not supported. Call it as a funct
|
||||
@@ -172,6 +213,7 @@ built-ins/Array/prototype/sort/S15.4.4.11_A4_T3.js # Array.prototype.sort calle
|
||||
built-ins/Array/prototype/sort/S15.4.4.11_A6_T2.js # Array.prototype.sort called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/sort/bug_596_1.js # #1: [object, object].sort(); counter < 2. Actual: 0
|
||||
built-ins/Array/prototype/sort/precise-prototype-element.js # Expected SameValue(«undefined», «4») to be true
|
||||
built-ins/Array/prototype/splice/S15.4.4.12_A2.1_T5.js # Array.splice expects start to be a number.
|
||||
built-ins/Array/prototype/splice/S15.4.4.12_A2.2_T5.js # Array.splice expects delete count to be a number.
|
||||
built-ins/Array/prototype/splice/S15.4.4.12_A2_T1.js # Array.prototype.splice called on incompatible receiver a data object.
|
||||
built-ins/Array/prototype/splice/S15.4.4.12_A2_T2.js # Array.prototype.splice called on incompatible receiver a data object.
|
||||
@@ -199,8 +241,53 @@ built-ins/Iterator/prototype/drop/limit-tonumber-throws.js # Expected a Test262
|
||||
built-ins/Iterator/prototype/drop/limit-tonumber.js # Iterator.prototype.drop expects a non-negative count, received NaN.
|
||||
built-ins/Iterator/prototype/take/limit-tonumber-throws.js # Expected a Test262Error but got a RangeError
|
||||
built-ins/Iterator/prototype/take/limit-tonumber.js # Iterator.prototype.take expects a non-negative count, received NaN.
|
||||
language/statements/async-generator/dflt-params-abrupt.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dflt-params-ref-later.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dflt-params-ref-self.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-init-iter-get-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elem-ary-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elem-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elem-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elem-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elem-obj-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elem-obj-val-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-elision-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-rest-id-elision-next-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/ary-ptrn-rest-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-init-iter-get-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elem-ary-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elem-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elem-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elem-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elem-obj-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elem-obj-val-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-elision-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-rest-id-elision-next-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-ary-ptrn-rest-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-init-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-init-undefined.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-list-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-prop-ary-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-prop-eval-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-prop-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-prop-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-prop-obj-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/dflt-obj-ptrn-prop-obj-value-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-init-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-init-undefined.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-list-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-prop-ary-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-prop-eval-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-prop-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-prop-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-prop-obj-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/dstr/obj-ptrn-prop-obj-value-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/async-generator/return-undefined-implicit-and-explicit.js # Actual ["tick 1", "tick 2", "g1 ret", "g2 ret", "g3 ret", "g4 ret"] and expected ["tick 1", "g1 ret"
|
||||
language/statements/const/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/for-await-of/async-func-decl-dstr-obj-empty-bool.js # TypeError: Object destructuring requires a data object or array value, received a boolean.
|
||||
@@ -249,10 +336,63 @@ language/statements/function/S13.2_A1_T1.js # #1: __func.prototype !== undefine
|
||||
language/statements/function/S13.2_A1_T2.js # #1: __func.prototype !== undefined
|
||||
language/statements/function/S13.2_A4_T1.js # Unknown identifier '…'.
|
||||
language/statements/function/S13.2_A4_T2.js # #1: typeof __gunc.prototype === '…'. Actual: typeof __gunc.prototype ===undefined
|
||||
language/statements/function/S13_A3_T1.js # Unknown identifier '…'.
|
||||
language/statements/function/S13_A6_T1.js # Identifier '…' has already been declared.
|
||||
language/statements/function/S14_A2.js # Unknown identifier '…'.
|
||||
language/statements/function/S14_A5_T1.js # Identifier '…' has already been declared.
|
||||
language/statements/function/S14_A5_T2.js # Identifier '…' has already been declared.
|
||||
language/statements/function/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/function/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dflt-params-abrupt.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dflt-params-ref-later.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dflt-params-ref-self.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-init-iter-get-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elem-ary-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elem-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elem-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elem-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elem-obj-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elem-obj-val-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-elision-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-rest-id-elision-next-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/ary-ptrn-rest-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-init-iter-get-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elem-ary-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elem-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elem-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elem-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elem-obj-val-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elem-obj-val-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-elision-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-rest-id-elision-next-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-ary-ptrn-rest-id-iter-step-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-init-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-init-undefined.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-list-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-prop-ary-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-prop-eval-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-prop-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-prop-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-prop-obj-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/dflt-obj-ptrn-prop-obj-value-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-init-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-init-undefined.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-list-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-prop-ary-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-prop-eval-err.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-prop-id-init-throws.js # Expected a Test262Error to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-prop-id-init-unresolvable.js # Expected a ReferenceError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-prop-obj-value-null.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/dstr/obj-ptrn-prop-obj-value-undef.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/generators/has-instance.js # The right-hand side of '…' has no '…' object.
|
||||
language/statements/generators/prototype-typeof.js # Expected SameValue(«"undefined"», «"object"») to be true
|
||||
language/statements/generators/prototype-uniqueness.js # Expected true but got false
|
||||
language/statements/labeled/value-await-non-module.js # Failed to parse TypeScript: Expression expected.
|
||||
language/statements/let/dstr/ary-init-iter-get-err-array-prototype.js # Expected a TypeError to be thrown but no exception was thrown at all
|
||||
language/statements/return/S12.9_A1_T1.js # expected SyntaxError but the program ran
|
||||
|
||||
@@ -106,12 +106,9 @@ describe("callable namespaces", () => {
|
||||
const diagnostic = await failure(runtime, `return await tools.issues.missing({})`)
|
||||
expect(diagnostic.kind).toBe("UnknownTool")
|
||||
expect(diagnostic.message).toContain("Unknown tool 'issues.missing'")
|
||||
expect(diagnostic.suggestions).toEqual(["Use search to find available tools."])
|
||||
})
|
||||
|
||||
test("an unknown tool names the closest match", async () => {
|
||||
const diagnostic = await failure(runtime, `return await tools.issues["get-list"]({})`)
|
||||
expect(diagnostic.message).toBe("Unknown tool 'issues.get-list'. Did you mean tools.issues.list?")
|
||||
expect(diagnostic.suggestions).toEqual([
|
||||
"The tool may have been removed or renamed. Use search to find available tools.",
|
||||
])
|
||||
})
|
||||
|
||||
test("a namespace without its own tool stays non-callable", async () => {
|
||||
|
||||
@@ -234,44 +234,6 @@ describe("var semantics beyond Test262", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("function declarations and expressions", () => {
|
||||
test("the last of repeated function declarations wins, and a var may share the name", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
function f() { return 1 }
|
||||
const first = f()
|
||||
function f() { return 2 }
|
||||
var g = 1
|
||||
function g() { return 3 }
|
||||
return [first, f(), typeof g]
|
||||
`),
|
||||
).toEqual([2, 2, "number"])
|
||||
})
|
||||
|
||||
test("a named function expression sees its own name inside its body, read-only", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
const fact = function inner(n) { return n <= 1 ? 1 : n * inner(n - 1) }
|
||||
const reassign = function inner() { inner = 5 }
|
||||
let failure
|
||||
try { reassign() } catch (error) { failure = error.constructor.name }
|
||||
return [fact(4), typeof inner, failure]
|
||||
`),
|
||||
).toEqual([24, "undefined", "TypeError"])
|
||||
})
|
||||
|
||||
test("generator functions have their own prototype", async () => {
|
||||
expect(
|
||||
await value(`
|
||||
function* g() {}
|
||||
async function* ag() {}
|
||||
function f() {}
|
||||
return [g() instanceof g, ag() instanceof ag, g.prototype === ag.prototype, typeof g.prototype, typeof f.prototype]
|
||||
`),
|
||||
).toEqual([true, true, false, "object", "undefined"])
|
||||
})
|
||||
})
|
||||
|
||||
describe("switch case function hoisting", () => {
|
||||
test("function declarations are visible across all cases before their statement runs", async () => {
|
||||
expect(await value(`switch (1) { case 1: return foo(); function foo() { return "hoisted" } }`)).toBe("hoisted")
|
||||
|
||||
@@ -254,7 +254,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | نماذج برمجة منخفضة التكلفة للجميع",
|
||||
"go.banner.text": "يحصل DeepSeek V4 Flash على حدود استخدام مضاعفة لفترة محدودة",
|
||||
"go.meta.description":
|
||||
"يبلغ سعر Go $10/شهر، مع حدود استخدام سخية ووصول موثوق إلى نماذج البرمجة الرائدة.",
|
||||
"يبدأ Go بسعر $5 للشهر الأول، ثم $10/شهر، مع حدود استخدام سخية ووصول موثوق إلى نماذج البرمجة الرائدة.",
|
||||
"go.hero.title": "نماذج برمجة منخفضة التكلفة للجميع",
|
||||
"go.hero.body":
|
||||
"يجلب Go البرمجة الوكيلة للمبرمجين حول العالم. يوفر حدودًا سخية ووصولًا موثوقًا إلى أقوى النماذج مفتوحة المصدر، حتى تتمكن من البناء باستخدام وكلاء أقوياء دون القلق بشأن التكلفة أو التوفر.",
|
||||
@@ -263,8 +263,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "اشترك في Go",
|
||||
"go.cta.price": "$10/شهر",
|
||||
"go.cta.promo": "$5 للشهر الأول",
|
||||
"go.pricing.body":
|
||||
"استخدمه مع أي وكيل. $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.",
|
||||
"استخدمه مع أي وكيل. $5 للشهر الأول، ثم $10/شهر. قم بزيادة الرصيد إذا لزم الأمر. الإلغاء في أي وقت.",
|
||||
"go.graph.free": "مجاني",
|
||||
"go.graph.freePill": "Big Pickle ونماذج مجانية",
|
||||
"go.graph.go": "Go",
|
||||
@@ -295,20 +296,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "أتمنى لو كنت لا أزال في Nvidia.",
|
||||
"go.problem.title": "ما المشكلة التي يحلها Go؟",
|
||||
"go.problem.body":
|
||||
"نحن نركز على تقديم تجربة OpenCode لأكبر عدد ممكن من الناس. OpenCode Go هو اشتراك منخفض التكلفة بسعر $10/شهر. يوفر حدودا سخية ووصولا موثوقا إلى نماذج المصدر المفتوح الأكثر قدرة.",
|
||||
"نحن نركز على تقديم تجربة OpenCode لأكبر عدد ممكن من الناس. OpenCode Go هو اشتراك منخفض التكلفة: $5 للشهر الأول، ثم $10/شهر. يوفر حدودا سخية ووصولا موثوقا إلى نماذج المصدر المفتوح الأكثر قدرة.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "أسعار اشتراك منخفضة التكلفة",
|
||||
"go.problem.item2": "حدود سخية ووصول موثوق",
|
||||
"go.problem.item3": "مصمم لأكبر عدد ممكن من المبرمجين",
|
||||
"go.problem.item4": "مجموعة منسقة من النماذج المختبرة للبرمجة الوكيلة",
|
||||
"go.how.title": "كيف يعمل Go",
|
||||
"go.how.body": "يبلغ سعر Go $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
||||
"go.how.body": "يبدأ Go من $5 للشهر الأول، ثم $10/شهر. يمكنك استخدامه مع OpenCode أو أي وكيل.",
|
||||
"go.how.step1.title": "أنشئ حسابًا",
|
||||
"go.how.step1.beforeLink": "اتبع",
|
||||
"go.how.step1.link": "تعليمات الإعداد",
|
||||
"go.how.step2.title": "اشترك في Go",
|
||||
"go.how.step2.link": "$10/شهر",
|
||||
"go.how.step2.afterLink": "مع حدود سخية",
|
||||
"go.how.step2.link": "$5 للشهر الأول",
|
||||
"go.how.step2.afterLink": "ثم $10/شهر مع حدود سخية",
|
||||
"go.how.step3.title": "ابدأ البرمجة",
|
||||
"go.how.step3.body": "مع وصول موثوق لنماذج مفتوحة المصدر",
|
||||
"go.privacy.title": "خصوصيتك مهمة بالنسبة لنا",
|
||||
@@ -324,11 +325,11 @@ export const dict = {
|
||||
"go.faq.a2": "يتضمن Go النماذج المدرجة أدناه، مع حدود سخية وإتاحة موثوقة.",
|
||||
"go.faq.q3": "هل Go هو نفسه Zen؟",
|
||||
"go.faq.a3":
|
||||
"لا. يعتمد Zen على الدفع حسب الاستخدام، بينما يبلغ سعر Go $10/شهر، مع حدود سخية ووصول موثوق إلى مجموعة منسقة من النماذج.",
|
||||
"لا. يعتمد Zen على الدفع حسب الاستخدام، بينما يبدأ Go بسعر $5 للشهر الأول، ثم $10/شهر، مع حدود سخية ووصول موثوق إلى مجموعة منسقة من النماذج.",
|
||||
"go.faq.q4": "كم تكلفة Go؟",
|
||||
"go.faq.a4.p1.beforePricing": "تكلفة Go",
|
||||
"go.faq.a4.p1.pricingLink": "$10/شهر",
|
||||
"go.faq.a4.p1.afterPricing": "مع حدود سخية.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 للشهر الأول",
|
||||
"go.faq.a4.p1.afterPricing": "ثم $10/شهر مع حدود سخية.",
|
||||
"go.faq.a4.p2.beforeAccount": "يمكنك إدارة اشتراكك في",
|
||||
"go.faq.a4.p2.accountLink": "حسابك",
|
||||
"go.faq.a4.p3": "ألغِ في أي وقت.",
|
||||
@@ -670,8 +671,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"عضو آخر في مساحة العمل هذه مشترك بالفعل في OpenCode Go. يمكن لعضو واحد فقط لكل مساحة عمل الاشتراك.",
|
||||
"workspace.lite.promo.description":
|
||||
"يبلغ سعر OpenCode Go {{price}}، ويوفر وصولا موثوقا لنماذج البرمجة المفتوحة الشهيرة مع حدود استخدام سخية.",
|
||||
"workspace.lite.promo.price": "$10/شهر",
|
||||
"يبدأ OpenCode Go بسعر {{price}}، ثم $10/شهر، ويوفر وصولا موثوقا لنماذج البرمجة المفتوحة الشهيرة مع حدود استخدام سخية.",
|
||||
"workspace.lite.promo.price": "$5 للشهر الأول",
|
||||
"workspace.lite.promo.modelsTitle": "ما يتضمنه",
|
||||
"workspace.lite.promo.footer":
|
||||
"صُممت الخطة بشكل أساسي للمستخدمين الدوليين، وتوفر وصولًا عالميًا مستقرًا. قد تتغير الأسعار وحدود الاستخدام بينما نتعلم من الاستخدام المبكر والملاحظات.",
|
||||
|
||||
@@ -258,7 +258,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Modelos de codificação de baixo custo para todos",
|
||||
"go.banner.text": "DeepSeek V4 Flash tem limites de uso 2x maiores por tempo limitado",
|
||||
"go.meta.description":
|
||||
"O Go custa $10/mês, com limites generosos de uso e acesso confiável aos principais modelos de codificação.",
|
||||
"O Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos de uso e acesso confiável aos principais modelos de codificação.",
|
||||
"go.hero.title": "Modelos de codificação de baixo custo para todos",
|
||||
"go.hero.body":
|
||||
"O Go traz a codificação com agentes para programadores em todo o mundo. Oferecendo limites generosos e acesso confiável aos modelos de código aberto mais capazes, para que você possa construir com agentes poderosos sem se preocupar com custos ou disponibilidade.",
|
||||
@@ -267,8 +267,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Assinar o Go",
|
||||
"go.cta.price": "$10/mês",
|
||||
"go.cta.promo": "$5 no primeiro mês",
|
||||
"go.pricing.body":
|
||||
"Use com qualquer agente. $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.",
|
||||
"Use com qualquer agente. $5 no primeiro mês, depois $10/mês. Recarregue o crédito se necessário. Cancele a qualquer momento.",
|
||||
"go.graph.free": "Grátis",
|
||||
"go.graph.freePill": "Big Pickle e modelos gratuitos",
|
||||
"go.graph.go": "Go",
|
||||
@@ -300,7 +301,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Eu queria ainda estar na Nvidia.",
|
||||
"go.problem.title": "Que problema o Go resolve?",
|
||||
"go.problem.body":
|
||||
"Estamos focados em levar a experiência do OpenCode para o maior número de pessoas possível. OpenCode Go é uma assinatura de baixo custo de $10/mês. Oferece limites generosos e acesso confiável aos modelos open source mais capazes.",
|
||||
"Estamos focados em levar a experiência do OpenCode para o maior número de pessoas possível. OpenCode Go é uma assinatura de baixo custo: $5 no primeiro mês, depois $10/mês. Oferece limites generosos e acesso confiável aos modelos open source mais capazes.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Preço de assinatura de baixo custo",
|
||||
"go.problem.item2": "Limites generosos e acesso confiável",
|
||||
@@ -308,13 +309,13 @@ export const dict = {
|
||||
"go.problem.item4": "Uma seleção de modelos testados para codificação com agentes",
|
||||
"go.how.title": "Como o Go funciona",
|
||||
"go.how.body":
|
||||
"O Go custa $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
||||
"O Go começa em $5 no primeiro mês, depois $10/mês. Você pode usá-lo com o OpenCode ou qualquer agente.",
|
||||
"go.how.step1.title": "Crie uma conta",
|
||||
"go.how.step1.beforeLink": "siga as",
|
||||
"go.how.step1.link": "instruções de configuração",
|
||||
"go.how.step2.title": "Assinar o Go",
|
||||
"go.how.step2.link": "$10/mês",
|
||||
"go.how.step2.afterLink": "com limites generosos",
|
||||
"go.how.step2.link": "$5 no primeiro mês",
|
||||
"go.how.step2.afterLink": "depois $10/mês com limites generosos",
|
||||
"go.how.step3.title": "Comece a codificar",
|
||||
"go.how.step3.body": "com acesso confiável a modelos de código aberto",
|
||||
"go.privacy.title": "Sua privacidade é importante para nós",
|
||||
@@ -331,11 +332,11 @@ export const dict = {
|
||||
"go.faq.a2": "O Go inclui os modelos listados abaixo, com limites generosos e acesso confiável.",
|
||||
"go.faq.q3": "O Go é o mesmo que o Zen?",
|
||||
"go.faq.a3":
|
||||
"Não. Zen é pay-as-you-go, enquanto o Go custa $10/mês, com limites generosos e acesso confiável a uma seleção de modelos.",
|
||||
"Não. Zen é pay-as-you-go, enquanto o Go começa em $5 no primeiro mês, depois $10/mês, com limites generosos e acesso confiável a uma seleção de modelos.",
|
||||
"go.faq.q4": "Quanto custa o Go?",
|
||||
"go.faq.a4.p1.beforePricing": "O Go custa",
|
||||
"go.faq.a4.p1.pricingLink": "$10/mês",
|
||||
"go.faq.a4.p1.afterPricing": "com limites generosos.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 no primeiro mês",
|
||||
"go.faq.a4.p1.afterPricing": "depois $10/mês com limites generosos.",
|
||||
"go.faq.a4.p2.beforeAccount": "Você pode gerenciar sua assinatura em sua",
|
||||
"go.faq.a4.p2.accountLink": "conta",
|
||||
"go.faq.a4.p3": "Cancele a qualquer momento.",
|
||||
@@ -681,8 +682,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Outro membro neste workspace já assina o OpenCode Go. Apenas um membro por workspace pode assinar.",
|
||||
"workspace.lite.promo.description":
|
||||
"O OpenCode Go custa {{price}} e oferece acesso confiável a modelos de codificação abertos populares com limites de uso generosos.",
|
||||
"workspace.lite.promo.price": "$10/mês",
|
||||
"O OpenCode Go começa em {{price}}, depois $10/mês, e oferece acesso confiável a modelos de codificação abertos populares com limites de uso generosos.",
|
||||
"workspace.lite.promo.price": "$5 no primeiro mês",
|
||||
"workspace.lite.promo.modelsTitle": "O que está incluído",
|
||||
"workspace.lite.promo.footer":
|
||||
"O plano foi desenvolvido principalmente para usuários internacionais e oferece acesso global estável. Os preços e limites de uso podem mudar à medida que aprendemos com o uso inicial e o feedback recebido.",
|
||||
|
||||
@@ -256,7 +256,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Kodningsmodeller til lav pris for alle",
|
||||
"go.banner.text": "DeepSeek V4 Flash får fordoblet brugsgrænse i en begrænset periode",
|
||||
"go.meta.description":
|
||||
"Go koster $10/måned, med generøse brugsgrænser og pålidelig adgang til førende kodningsmodeller.",
|
||||
"Go starter ved $5 for den første måned, derefter $10/måned, med generøse brugsgrænser og pålidelig adgang til førende kodningsmodeller.",
|
||||
"go.hero.title": "Kodningsmodeller til lav pris for alle",
|
||||
"go.hero.body":
|
||||
"Go bringer agentisk kodning til programmører over hele verden. Med generøse grænser og pålidelig adgang til de mest kapable open source-modeller, så du kan bygge med kraftfulde agenter uden at bekymre dig om omkostninger eller tilgængelighed.",
|
||||
@@ -265,8 +265,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Abonner på Go",
|
||||
"go.cta.price": "$10/måned",
|
||||
"go.cta.promo": "$5 første måned",
|
||||
"go.pricing.body":
|
||||
"Brug med enhver agent. $10/måned. Tank op med kredit efter behov. Afmeld når som helst.",
|
||||
"Brug med enhver agent. $5 første måned, derefter $10/måned. Tank op med kredit efter behov. Afmeld når som helst.",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||
"go.graph.go": "Go",
|
||||
@@ -297,7 +298,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Jeg ville ønske, jeg stadig var hos Nvidia.",
|
||||
"go.problem.title": "Hvilket problem løser Go?",
|
||||
"go.problem.body":
|
||||
"Vi fokuserer på at bringe OpenCode-oplevelsen ud til så mange som muligt. OpenCode Go er et lavprisabonnement til $10/måned. Det giver generøse grænser og pålidelig adgang til de mest kapable open source-modeller.",
|
||||
"Vi fokuserer på at bringe OpenCode-oplevelsen ud til så mange som muligt. OpenCode Go er et lavprisabonnement: $5 for den første måned, derefter $10/måned. Det giver generøse grænser og pålidelig adgang til de mest kapable open source-modeller.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Lavpris abonnementspriser",
|
||||
"go.problem.item2": "Generøse grænser og pålidelig adgang",
|
||||
@@ -305,13 +306,13 @@ export const dict = {
|
||||
"go.problem.item4": "Et kurateret modeludvalg testet til agentisk kodning",
|
||||
"go.how.title": "Hvordan Go virker",
|
||||
"go.how.body":
|
||||
"Go koster $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
||||
"Go starter ved $5 for den første måned, derefter $10/måned. Du kan bruge det med OpenCode eller enhver agent.",
|
||||
"go.how.step1.title": "Opret en konto",
|
||||
"go.how.step1.beforeLink": "følg",
|
||||
"go.how.step1.link": "opsætningsinstruktionerne",
|
||||
"go.how.step2.title": "Abonner på Go",
|
||||
"go.how.step2.link": "$10/måned",
|
||||
"go.how.step2.afterLink": "med generøse grænser",
|
||||
"go.how.step2.link": "$5 første måned",
|
||||
"go.how.step2.afterLink": "derefter $10/måned med generøse grænser",
|
||||
"go.how.step3.title": "Start kodning",
|
||||
"go.how.step3.body": "med pålidelig adgang til open source-modeller",
|
||||
"go.privacy.title": "Dit privatliv er vigtigt for os",
|
||||
@@ -328,11 +329,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go inkluderer modellerne nedenfor med generøse grænser og pålidelig adgang.",
|
||||
"go.faq.q3": "Er Go det samme som Zen?",
|
||||
"go.faq.a3":
|
||||
"Nej. Zen er pay-as-you-go, mens Go koster $10/måned, med generøse grænser og pålidelig adgang til et kurateret modeludvalg.",
|
||||
"Nej. Zen er pay-as-you-go, mens Go starter ved $5 for den første måned, derefter $10/måned, med generøse grænser og pålidelig adgang til et kurateret modeludvalg.",
|
||||
"go.faq.q4": "Hvad koster Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||
"go.faq.a4.p1.pricingLink": "$10/måned",
|
||||
"go.faq.a4.p1.afterPricing": "med generøse grænser.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||
"go.faq.a4.p1.afterPricing": "derefter $10/måned med generøse grænser.",
|
||||
"go.faq.a4.p2.beforeAccount": "Du kan administrere dit abonnement i din",
|
||||
"go.faq.a4.p2.accountLink": "konto",
|
||||
"go.faq.a4.p3": "Annuller til enhver tid.",
|
||||
@@ -678,8 +679,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Et andet medlem i dette workspace abonnerer allerede på OpenCode Go. Kun ét medlem pr. workspace kan abonnere.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go koster {{price}} og giver pålidelig adgang til populære åbne kodningsmodeller med generøse brugsgrænser.",
|
||||
"workspace.lite.promo.price": "$10/måned",
|
||||
"OpenCode Go starter ved {{price}}, derefter $10/måned, og giver pålidelig adgang til populære åbne kodningsmodeller med generøse brugsgrænser.",
|
||||
"workspace.lite.promo.price": "$5 for den første måned",
|
||||
"workspace.lite.promo.modelsTitle": "Hvad er inkluderet",
|
||||
"workspace.lite.promo.footer":
|
||||
"Planen er primært udviklet til internationale brugere og giver stabil adgang i hele verden. Priser og forbrugsgrænser kan ændre sig, efterhånden som vi lærer af de første brugserfaringer og tilbagemeldinger.",
|
||||
|
||||
@@ -258,7 +258,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Kostengünstige Coding-Modelle für alle",
|
||||
"go.banner.text": "DeepSeek V4 Flash erhält für begrenzte Zeit 2x Nutzungslimits",
|
||||
"go.meta.description":
|
||||
"Go kostet $10/Monat, mit großzügigen Nutzungslimits und zuverlässigem Zugang zu führenden Coding-Modellen.",
|
||||
"Go beginnt bei $5 für deinen ersten Monat, danach $10/Monat, mit großzügigen Nutzungslimits und zuverlässigem Zugang zu führenden Coding-Modellen.",
|
||||
"go.hero.title": "Kostengünstige Coding-Modelle für alle",
|
||||
"go.hero.body":
|
||||
"Go bringt Agentic Coding zu Programmierern auf der ganzen Welt. Mit großzügigen Limits und zuverlässigem Zugang zu den leistungsfähigsten Open-Source-Modellen, damit du mit leistungsstarken Agenten entwickeln kannst, ohne dir Gedanken über Kosten oder Verfügbarkeit zu machen.",
|
||||
@@ -267,8 +267,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Go abonnieren",
|
||||
"go.cta.price": "$10/Monat",
|
||||
"go.cta.promo": "$5 im ersten Monat",
|
||||
"go.pricing.body":
|
||||
"Mit jedem Agenten nutzbar. $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.",
|
||||
"Mit jedem Agenten nutzbar. $5 im ersten Monat, danach $10/Monat. Guthaben bei Bedarf aufladen. Jederzeit kündbar.",
|
||||
"go.graph.free": "Kostenlos",
|
||||
"go.graph.freePill": "Big Pickle und kostenlose Modelle",
|
||||
"go.graph.go": "Go",
|
||||
@@ -299,7 +300,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Ich wünschte, ich wäre noch bei Nvidia.",
|
||||
"go.problem.title": "Welches Problem löst Go?",
|
||||
"go.problem.body":
|
||||
"Wir konzentrieren uns darauf, die OpenCode-Erfahrung so vielen Menschen wie möglich zugänglich zu machen. OpenCode Go ist ein kostengünstiges Abonnement für $10/Monat. Es bietet großzügige Limits und zuverlässigen Zugang zu den leistungsfähigsten Open-Source-Modellen.",
|
||||
"Wir konzentrieren uns darauf, die OpenCode-Erfahrung so vielen Menschen wie möglich zugänglich zu machen. OpenCode Go ist ein kostengünstiges Abonnement: $5 im ersten Monat, danach $10/Monat. Es bietet großzügige Limits und zuverlässigen Zugang zu den leistungsfähigsten Open-Source-Modellen.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Kostengünstiges Abonnement",
|
||||
"go.problem.item2": "Großzügige Limits und zuverlässiger Zugang",
|
||||
@@ -307,13 +308,13 @@ export const dict = {
|
||||
"go.problem.item4": "Eine kuratierte, für Agentic Coding getestete Modellauswahl",
|
||||
"go.how.title": "Wie Go funktioniert",
|
||||
"go.how.body":
|
||||
"Go kostet $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
||||
"Go beginnt bei $5 für den ersten Monat, danach $10/Monat. Du kannst es mit OpenCode oder jedem Agenten nutzen.",
|
||||
"go.how.step1.title": "Konto erstellen",
|
||||
"go.how.step1.beforeLink": "folge den",
|
||||
"go.how.step1.link": "Einrichtungsanweisungen",
|
||||
"go.how.step2.title": "Go abonnieren",
|
||||
"go.how.step2.link": "$10/Monat",
|
||||
"go.how.step2.afterLink": "mit großzügigen Limits",
|
||||
"go.how.step2.link": "$5 im ersten Monat",
|
||||
"go.how.step2.afterLink": "danach $10/Monat mit großzügigen Limits",
|
||||
"go.how.step3.title": "Loslegen mit Coding",
|
||||
"go.how.step3.body": "mit zuverlässigem Zugang zu Open-Source-Modellen",
|
||||
"go.privacy.title": "Deine Privatsphäre ist uns wichtig",
|
||||
@@ -330,11 +331,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go umfasst die unten aufgeführten Modelle mit großzügigen Limits und zuverlässigem Zugriff.",
|
||||
"go.faq.q3": "Ist Go dasselbe wie Zen?",
|
||||
"go.faq.a3":
|
||||
"Nein. Zen ist Pay-as-you-go, während Go $10/Monat kostet, mit großzügigen Limits und zuverlässigem Zugang zu einer kuratierten Modellauswahl.",
|
||||
"Nein. Zen ist Pay-as-you-go, während Go bei $5 für deinen ersten Monat beginnt, danach $10/Monat, mit großzügigen Limits und zuverlässigem Zugang zu einer kuratierten Modellauswahl.",
|
||||
"go.faq.q4": "Wie viel kostet Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go kostet",
|
||||
"go.faq.a4.p1.pricingLink": "$10/Monat",
|
||||
"go.faq.a4.p1.afterPricing": "mit großzügigen Limits.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 im ersten Monat",
|
||||
"go.faq.a4.p1.afterPricing": "danach $10/Monat mit großzügigen Limits.",
|
||||
"go.faq.a4.p2.beforeAccount": "Du kannst dein Abonnement in deinem",
|
||||
"go.faq.a4.p2.accountLink": "Konto verwalten",
|
||||
"go.faq.a4.p3": "Jederzeit kündbar.",
|
||||
@@ -680,8 +681,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Ein anderes Mitglied in diesem Workspace hat OpenCode Go bereits abonniert. Nur ein Mitglied pro Workspace kann abonnieren.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go kostet {{price}} und bietet zuverlässigen Zugang zu beliebten offenen Coding-Modellen mit großzügigen Nutzungslimits.",
|
||||
"workspace.lite.promo.price": "$10/Monat",
|
||||
"OpenCode Go startet bei {{price}}, danach $10/Monat, und bietet zuverlässigen Zugang zu beliebten offenen Coding-Modellen mit großzügigen Nutzungslimits.",
|
||||
"workspace.lite.promo.price": "$5 im ersten Monat",
|
||||
"workspace.lite.promo.modelsTitle": "Was enthalten ist",
|
||||
"workspace.lite.promo.footer":
|
||||
"Der Plan richtet sich in erster Linie an internationale Nutzer und bietet stabilen weltweiten Zugriff. Preise und Nutzungslimits können sich ändern, wenn wir Erkenntnisse aus der ersten Nutzung und dem Feedback gewinnen.",
|
||||
|
||||
@@ -255,7 +255,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Low cost coding models for everyone",
|
||||
"go.banner.text": "DeepSeek V4 Flash gets 2× usage limits for a limited time",
|
||||
"go.meta.description":
|
||||
"Go costs $10/month, with generous usage limits and reliable access to leading coding models.",
|
||||
"Go starts at $5 for your first month, then $10/month, with generous usage limits and reliable access to leading coding models.",
|
||||
"go.hero.title": "Low cost coding models for everyone",
|
||||
"go.hero.body":
|
||||
"Go brings agentic coding to programmers around the world. Offering generous limits and reliable access to the most capable open-source models, so you can build with powerful agents without worrying about cost or availability.",
|
||||
@@ -264,7 +264,8 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Subscribe to Go",
|
||||
"go.cta.price": "$10/month",
|
||||
"go.pricing.body": "Use with any agent. $10/month. Top up credit if needed. Cancel any time.",
|
||||
"go.cta.promo": "$5 first month",
|
||||
"go.pricing.body": "Use with any agent. $5 first month, then $10/month. Top up credit if needed. Cancel any time.",
|
||||
"go.graph.free": "Free",
|
||||
"go.graph.freePill": "Big Pickle and free models",
|
||||
"go.graph.go": "Go",
|
||||
@@ -296,20 +297,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "I wish I was still at Nvidia.",
|
||||
"go.problem.title": "What problem is Go solving?",
|
||||
"go.problem.body":
|
||||
"We're focused on bringing the OpenCode experience to as many people as possible. OpenCode Go is a low cost $10/month subscription. It provides generous limits and reliable access to the most capable open source models.",
|
||||
"We're focused on bringing the OpenCode experience to as many people as possible. OpenCode Go is a low cost subscription: $5 for your first month, then $10/month. It provides generous limits and reliable access to the most capable open source models.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Low cost subscription pricing",
|
||||
"go.problem.item2": "Generous limits and reliable access",
|
||||
"go.problem.item3": "Built for as many programmers as possible",
|
||||
"go.problem.item4": "A curated model lineup tested for agentic coding",
|
||||
"go.how.title": "How Go works",
|
||||
"go.how.body": "Go costs $10/month. You can use it with OpenCode or any agent.",
|
||||
"go.how.body": "Go starts at $5 for your first month, then $10/month. You can use it with OpenCode or any agent.",
|
||||
"go.how.step1.title": "Create an account",
|
||||
"go.how.step1.beforeLink": "follow the",
|
||||
"go.how.step1.link": "setup instructions",
|
||||
"go.how.step2.title": "Subscribe to Go",
|
||||
"go.how.step2.link": "$10/month",
|
||||
"go.how.step2.afterLink": "with generous limits",
|
||||
"go.how.step2.link": "$5 first month",
|
||||
"go.how.step2.afterLink": "then $10/month with generous limits",
|
||||
"go.how.step3.title": "Start coding",
|
||||
"go.how.step3.body": "with reliable access to open-source models",
|
||||
"go.privacy.title": "Your privacy is important to us",
|
||||
@@ -326,11 +327,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go includes the models listed below, with generous limits and reliable access.",
|
||||
"go.faq.q3": "Is Go the same as Zen?",
|
||||
"go.faq.a3":
|
||||
"No. Zen is pay-as-you-go, while Go costs $10/month, with generous limits and reliable access to a curated model lineup.",
|
||||
"No. Zen is pay-as-you-go, while Go starts at $5 for your first month, then $10/month, with generous limits and reliable access to a curated model lineup.",
|
||||
"go.faq.q4": "How much does Go cost?",
|
||||
"go.faq.a4.p1.beforePricing": "Go costs",
|
||||
"go.faq.a4.p1.pricingLink": "$10/month",
|
||||
"go.faq.a4.p1.afterPricing": "with generous limits.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 first month",
|
||||
"go.faq.a4.p1.afterPricing": "then $10/month with generous limits.",
|
||||
"go.faq.a4.p2.beforeAccount": "You can manage your subscription in your",
|
||||
"go.faq.a4.p2.accountLink": "account",
|
||||
"go.faq.a4.p3": "Cancel any time.",
|
||||
@@ -676,8 +677,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Another member in this workspace is already subscribed to OpenCode Go. Only one member per workspace can subscribe.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go costs {{price}} and provides reliable access to popular open coding models with generous usage limits.",
|
||||
"workspace.lite.promo.price": "$10/month",
|
||||
"OpenCode Go starts at {{price}}, then $10/month, and provides reliable access to popular open coding models with generous usage limits.",
|
||||
"workspace.lite.promo.price": "$5 for your first month",
|
||||
"workspace.lite.promo.modelsTitle": "What's Included",
|
||||
"workspace.lite.promo.footer":
|
||||
"The plan is designed primarily for international users and provides stable global access. Pricing and usage limits may change as we learn from early usage and feedback.",
|
||||
|
||||
@@ -259,7 +259,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Modelos de programación de bajo coste para todos",
|
||||
"go.banner.text": "DeepSeek V4 Flash tiene límites de uso 2x mayores por tiempo limitado",
|
||||
"go.meta.description":
|
||||
"Go cuesta 10 $/mes, con límites de uso generosos y acceso fiable a modelos de programación líderes.",
|
||||
"Go comienza en $5 el primer mes, luego 10 $/mes, con límites de uso generosos y acceso fiable a modelos de programación líderes.",
|
||||
"go.hero.title": "Modelos de programación de bajo coste para todos",
|
||||
"go.hero.body":
|
||||
"Go lleva la programación agéntica a programadores de todo el mundo. Ofrece límites generosos y acceso fiable a los modelos de código abierto más capaces, para que puedas crear con agentes potentes sin preocuparte por el coste o la disponibilidad.",
|
||||
@@ -268,8 +268,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Suscribirse a Go",
|
||||
"go.cta.price": "10 $/mes",
|
||||
"go.cta.promo": "$5 el primer mes",
|
||||
"go.pricing.body":
|
||||
"Úsalo con cualquier agente. 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.",
|
||||
"Úsalo con cualquier agente. $5 el primer mes, luego 10 $/mes. Recarga crédito si es necesario. Cancela en cualquier momento.",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle y modelos gratuitos",
|
||||
"go.graph.go": "Go",
|
||||
@@ -301,20 +302,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Ojalá siguiera en Nvidia.",
|
||||
"go.problem.title": "¿Qué problema resuelve Go?",
|
||||
"go.problem.body":
|
||||
"Nos enfocamos en llevar la experiencia de OpenCode a tantas personas como sea posible. OpenCode Go es una suscripción de bajo coste de 10 $/mes. Proporciona límites generosos y acceso fiable a los modelos de código abierto más capaces.",
|
||||
"Nos enfocamos en llevar la experiencia de OpenCode a tantas personas como sea posible. OpenCode Go es una suscripción de bajo coste: $5 el primer mes, luego 10 $/mes. Proporciona límites generosos y acceso fiable a los modelos de código abierto más capaces.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Precios de suscripción de bajo coste",
|
||||
"go.problem.item2": "Límites generosos y acceso fiable",
|
||||
"go.problem.item3": "Creado para tantos programadores como sea posible",
|
||||
"go.problem.item4": "Una selección de modelos probados para programación agéntica",
|
||||
"go.how.title": "Cómo funciona Go",
|
||||
"go.how.body": "Go cuesta 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
||||
"go.how.body": "Go comienza en $5 el primer mes, luego 10 $/mes. Puedes usarlo con OpenCode o cualquier agente.",
|
||||
"go.how.step1.title": "Crear una cuenta",
|
||||
"go.how.step1.beforeLink": "sigue las",
|
||||
"go.how.step1.link": "instrucciones de configuración",
|
||||
"go.how.step2.title": "Suscribirse a Go",
|
||||
"go.how.step2.link": "10 $/mes",
|
||||
"go.how.step2.afterLink": "con límites generosos",
|
||||
"go.how.step2.link": "$5 el primer mes",
|
||||
"go.how.step2.afterLink": "luego 10 $/mes con límites generosos",
|
||||
"go.how.step3.title": "Empezar a programar",
|
||||
"go.how.step3.body": "con acceso fiable a modelos de código abierto",
|
||||
"go.privacy.title": "Tu privacidad es importante para nosotros",
|
||||
@@ -331,11 +332,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go incluye los modelos que se indican abajo, con límites generosos y acceso confiable.",
|
||||
"go.faq.q3": "¿Es Go lo mismo que Zen?",
|
||||
"go.faq.a3":
|
||||
"No. Zen es de pago por uso, mientras que Go cuesta 10 $/mes, con límites generosos y acceso fiable a una selección de modelos.",
|
||||
"No. Zen es de pago por uso, mientras que Go comienza en $5 el primer mes, luego 10 $/mes, con límites generosos y acceso fiable a una selección de modelos.",
|
||||
"go.faq.q4": "¿Cuánto cuesta Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go cuesta",
|
||||
"go.faq.a4.p1.pricingLink": "10 $/mes",
|
||||
"go.faq.a4.p1.afterPricing": "con límites generosos.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 el primer mes",
|
||||
"go.faq.a4.p1.afterPricing": "luego 10 $/mes con límites generosos.",
|
||||
"go.faq.a4.p2.beforeAccount": "Puedes gestionar tu suscripción en tu",
|
||||
"go.faq.a4.p2.accountLink": "cuenta",
|
||||
"go.faq.a4.p3": "Cancela en cualquier momento.",
|
||||
@@ -681,8 +682,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Otro miembro de este espacio de trabajo ya está suscrito a OpenCode Go. Solo un miembro por espacio de trabajo puede suscribirse.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go cuesta {{price}} y ofrece acceso confiable a modelos de codificación abiertos populares con límites de uso generosos.",
|
||||
"workspace.lite.promo.price": "$10/mes",
|
||||
"OpenCode Go comienza en {{price}}, luego $10/mes, y ofrece acceso confiable a modelos de codificación abiertos populares con límites de uso generosos.",
|
||||
"workspace.lite.promo.price": "$5 el primer mes",
|
||||
"workspace.lite.promo.modelsTitle": "Qué incluye",
|
||||
"workspace.lite.promo.footer":
|
||||
"El plan está diseñado principalmente para usuarios internacionales y ofrece un acceso global estable. Los precios y los límites de uso pueden cambiar a medida que aprendemos del uso inicial y de los comentarios recibidos.",
|
||||
|
||||
@@ -260,7 +260,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Modèles de code à faible coût pour tous",
|
||||
"go.banner.text": "DeepSeek V4 Flash bénéficie de limites d’utilisation 2x supérieures pour une durée limitée",
|
||||
"go.meta.description":
|
||||
"Go coûte 10 $/mois, avec des limites d'utilisation généreuses et un accès fiable aux principaux modèles de codage.",
|
||||
"Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites d'utilisation généreuses et un accès fiable aux principaux modèles de codage.",
|
||||
"go.hero.title": "Modèles de code à faible coût pour tous",
|
||||
"go.hero.body":
|
||||
"Go apporte le codage agentique aux programmeurs du monde entier. Offrant des limites généreuses et un accès fiable aux modèles open source les plus capables, pour que vous puissiez construire avec des agents puissants sans vous soucier du coût ou de la disponibilité.",
|
||||
@@ -269,8 +269,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "S'abonner à Go",
|
||||
"go.cta.price": "10 $/mois",
|
||||
"go.cta.promo": "$5 le premier mois",
|
||||
"go.pricing.body":
|
||||
"Utilisez-le avec n'importe quel agent. 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.",
|
||||
"Utilisez-le avec n'importe quel agent. $5 le premier mois, puis 10 $/mois. Rechargez du crédit si nécessaire. Annulez à tout moment.",
|
||||
"go.graph.free": "Gratuit",
|
||||
"go.graph.freePill": "Big Pickle et modèles gratuits",
|
||||
"go.graph.go": "Go",
|
||||
@@ -301,7 +302,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "J'aimerais être encore chez Nvidia.",
|
||||
"go.problem.title": "Quel problème Go résout-il ?",
|
||||
"go.problem.body":
|
||||
"Nous nous efforçons d'apporter l'expérience OpenCode au plus grand nombre. OpenCode Go est un abonnement à faible coût de 10 $/mois. Il offre des limites généreuses et un accès fiable aux modèles open source les plus performants.",
|
||||
"Nous nous efforçons d'apporter l'expérience OpenCode au plus grand nombre. OpenCode Go est un abonnement à faible coût : $5 pour le premier mois, puis 10 $/mois. Il offre des limites généreuses et un accès fiable aux modèles open source les plus performants.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Prix d'abonnement bas",
|
||||
"go.problem.item2": "Limites généreuses et accès fiable",
|
||||
@@ -309,13 +310,13 @@ export const dict = {
|
||||
"go.problem.item4": "Une sélection de modèles testés pour le codage agentique",
|
||||
"go.how.title": "Comment fonctionne Go",
|
||||
"go.how.body":
|
||||
"Go coûte 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.",
|
||||
"Go commence à $5 pour le premier mois, puis 10 $/mois. Vous pouvez l'utiliser avec OpenCode ou n'importe quel agent.",
|
||||
"go.how.step1.title": "Créez un compte",
|
||||
"go.how.step1.beforeLink": "suivez les",
|
||||
"go.how.step1.link": "instructions de configuration",
|
||||
"go.how.step2.title": "Abonnez-vous à Go",
|
||||
"go.how.step2.link": "10 $/mois",
|
||||
"go.how.step2.afterLink": "avec des limites généreuses",
|
||||
"go.how.step2.link": "$5 le premier mois",
|
||||
"go.how.step2.afterLink": "puis 10 $/mois avec des limites généreuses",
|
||||
"go.how.step3.title": "Commencez à coder",
|
||||
"go.how.step3.body": "avec un accès fiable aux modèles open source",
|
||||
"go.privacy.title": "Votre vie privée est importante pour nous",
|
||||
@@ -332,11 +333,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go inclut les modèles ci-dessous, avec des limites généreuses et un accès fiable.",
|
||||
"go.faq.q3": "Est-ce que Go est la même chose que Zen ?",
|
||||
"go.faq.a3":
|
||||
"Non. Zen est un paiement à l'utilisation, tandis que Go coûte 10 $/mois, avec des limites généreuses et un accès fiable à une sélection de modèles.",
|
||||
"Non. Zen est un paiement à l'utilisation, tandis que Go commence à $5 pour le premier mois, puis 10 $/mois, avec des limites généreuses et un accès fiable à une sélection de modèles.",
|
||||
"go.faq.q4": "Combien coûte Go ?",
|
||||
"go.faq.a4.p1.beforePricing": "Go coûte",
|
||||
"go.faq.a4.p1.pricingLink": "10 $/mois",
|
||||
"go.faq.a4.p1.afterPricing": "avec des limites généreuses.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 le premier mois",
|
||||
"go.faq.a4.p1.afterPricing": "puis 10 $/mois avec des limites généreuses.",
|
||||
"go.faq.a4.p2.beforeAccount": "Vous pouvez gérer votre abonnement dans votre",
|
||||
"go.faq.a4.p2.accountLink": "compte",
|
||||
"go.faq.a4.p3": "Annulez à tout moment.",
|
||||
@@ -688,8 +689,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Un autre membre de cet espace de travail est déjà abonné à OpenCode Go. Un seul membre par espace de travail peut s'abonner.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go coûte {{price}} et offre un accès fiable aux modèles de code ouverts populaires avec des limites d'utilisation généreuses.",
|
||||
"workspace.lite.promo.price": "10 $/mois",
|
||||
"OpenCode Go commence à {{price}}, puis 10 $/mois, et offre un accès fiable aux modèles de code ouverts populaires avec des limites d'utilisation généreuses.",
|
||||
"workspace.lite.promo.price": "$5 le premier mois",
|
||||
"workspace.lite.promo.modelsTitle": "Ce qui est inclus",
|
||||
"workspace.lite.promo.footer":
|
||||
"Ce forfait est principalement conçu pour les utilisateurs internationaux et offre un accès mondial stable. Les tarifs et les limites d'utilisation peuvent évoluer à mesure que nous tirons les enseignements des premières utilisations et des retours reçus.",
|
||||
|
||||
@@ -256,7 +256,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Modelli di coding a basso costo per tutti",
|
||||
"go.banner.text": "DeepSeek V4 Flash offre limiti di utilizzo 2x superiori per un periodo limitato",
|
||||
"go.meta.description":
|
||||
"Go costa $10/mese, con limiti di utilizzo generosi e un accesso affidabile ai principali modelli di coding.",
|
||||
"Go inizia a $5 per il primo mese, poi $10/mese, con limiti di utilizzo generosi e un accesso affidabile ai principali modelli di coding.",
|
||||
"go.hero.title": "Modelli di coding a basso costo per tutti",
|
||||
"go.hero.body":
|
||||
"Go porta il coding agentico ai programmatori di tutto il mondo. Offrendo limiti generosi e un accesso affidabile ai modelli open source più capaci, in modo da poter costruire con agenti potenti senza preoccuparsi dei costi o della disponibilità.",
|
||||
@@ -265,8 +265,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Abbonati a Go",
|
||||
"go.cta.price": "$10/mese",
|
||||
"go.cta.promo": "$5 il primo mese",
|
||||
"go.pricing.body":
|
||||
"Usalo con qualsiasi agente. $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.",
|
||||
"Usalo con qualsiasi agente. $5 il primo mese, poi $10/mese. Ricarica il credito se necessario. Annulla in qualsiasi momento.",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle e modelli gratuiti",
|
||||
"go.graph.go": "Go",
|
||||
@@ -297,20 +298,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Vorrei essere ancora a Nvidia.",
|
||||
"go.problem.title": "Quale problema risolve Go?",
|
||||
"go.problem.body":
|
||||
"Ci concentriamo nel portare l'esperienza OpenCode a quante più persone possibile. OpenCode Go è un abbonamento a basso costo da $10/mese. Offre limiti generosi e accesso affidabile ai modelli open source più capaci.",
|
||||
"Ci concentriamo nel portare l'esperienza OpenCode a quante più persone possibile. OpenCode Go è un abbonamento a basso costo: $5 il primo mese, poi $10/mese. Offre limiti generosi e accesso affidabile ai modelli open source più capaci.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Prezzo di abbonamento a basso costo",
|
||||
"go.problem.item2": "Limiti generosi e accesso affidabile",
|
||||
"go.problem.item3": "Costruito per il maggior numero possibile di programmatori",
|
||||
"go.problem.item4": "Una selezione curata di modelli testati per il coding agentico",
|
||||
"go.how.title": "Come funziona Go",
|
||||
"go.how.body": "Go costa $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.",
|
||||
"go.how.body": "Go inizia a $5 per il primo mese, poi $10/mese. Puoi usarlo con OpenCode o qualsiasi agente.",
|
||||
"go.how.step1.title": "Crea un account",
|
||||
"go.how.step1.beforeLink": "segui le",
|
||||
"go.how.step1.link": "istruzioni di configurazione",
|
||||
"go.how.step2.title": "Abbonati a Go",
|
||||
"go.how.step2.link": "$10/mese",
|
||||
"go.how.step2.afterLink": "con limiti generosi",
|
||||
"go.how.step2.link": "$5 il primo mese",
|
||||
"go.how.step2.afterLink": "poi $10/mese con limiti generosi",
|
||||
"go.how.step3.title": "Inizia a programmare",
|
||||
"go.how.step3.body": "con accesso affidabile ai modelli open source",
|
||||
"go.privacy.title": "La tua privacy è importante per noi",
|
||||
@@ -327,11 +328,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go include i modelli elencati di seguito, con limiti generosi e accesso affidabile.",
|
||||
"go.faq.q3": "Go è lo stesso di Zen?",
|
||||
"go.faq.a3":
|
||||
"No. Zen è a consumo, mentre Go costa $10/mese, con limiti generosi e un accesso affidabile a una selezione curata di modelli.",
|
||||
"No. Zen è a consumo, mentre Go inizia a $5 per il primo mese, poi $10/mese, con limiti generosi e un accesso affidabile a una selezione curata di modelli.",
|
||||
"go.faq.q4": "Quanto costa Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go costa",
|
||||
"go.faq.a4.p1.pricingLink": "$10/mese",
|
||||
"go.faq.a4.p1.afterPricing": "con limiti generosi.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 il primo mese",
|
||||
"go.faq.a4.p1.afterPricing": "poi $10/mese con limiti generosi.",
|
||||
"go.faq.a4.p2.beforeAccount": "Puoi gestire il tuo abbonamento nel tuo",
|
||||
"go.faq.a4.p2.accountLink": "account",
|
||||
"go.faq.a4.p3": "Annulla in qualsiasi momento.",
|
||||
@@ -679,8 +680,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Un altro membro in questo workspace è già abbonato a OpenCode Go. Solo un membro per workspace può abbonarsi.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go costa {{price}} e offre un accesso affidabile a popolari modelli di coding aperti con generosi limiti di utilizzo.",
|
||||
"workspace.lite.promo.price": "$10/mese",
|
||||
"OpenCode Go parte da {{price}}, poi $10/mese, e offre un accesso affidabile a popolari modelli di coding aperti con generosi limiti di utilizzo.",
|
||||
"workspace.lite.promo.price": "$5 il primo mese",
|
||||
"workspace.lite.promo.modelsTitle": "Cosa è incluso",
|
||||
"workspace.lite.promo.footer":
|
||||
"Il piano è pensato principalmente per gli utenti internazionali e offre un accesso globale stabile. I prezzi e i limiti di utilizzo potrebbero cambiare in base a quanto apprenderemo dall'utilizzo iniziale e dai feedback.",
|
||||
|
||||
@@ -255,7 +255,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | すべての人のための低価格なコーディングモデル",
|
||||
"go.banner.text": "DeepSeek V4 Flashの利用上限が期間限定で2倍に",
|
||||
"go.meta.description":
|
||||
"Goは月額$10で、主要なコーディングモデルへのゆとりある利用上限と安定したアクセスを提供します。",
|
||||
"Goは最初の月$5、その後$10/月で、主要なコーディングモデルへのゆとりある利用上限と安定したアクセスを提供します。",
|
||||
"go.hero.title": "すべての人のための低価格なコーディングモデル",
|
||||
"go.hero.body":
|
||||
"Goは、世界中のプログラマーにエージェント型コーディングをもたらします。最も高性能なオープンソースモデルへの十分な制限と安定したアクセスを提供し、コストや可用性を気にすることなく強力なエージェントで構築できます。",
|
||||
@@ -264,8 +264,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Goを購読する",
|
||||
"go.cta.price": "$10/月",
|
||||
"go.cta.promo": "初月 $5",
|
||||
"go.pricing.body":
|
||||
"どのエージェントでも使えます。月額$10。必要に応じてクレジットを追加。いつでもキャンセルできます。",
|
||||
"どのエージェントでも使えます。最初の月$5、その後$10/月。必要に応じてクレジットを追加。いつでもキャンセルできます。",
|
||||
"go.graph.free": "無料",
|
||||
"go.graph.freePill": "Big Pickleと無料モデル",
|
||||
"go.graph.go": "Go",
|
||||
@@ -297,20 +298,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "まだNvidiaにいられたらよかったのに。",
|
||||
"go.problem.title": "Goはどのような問題を解決していますか?",
|
||||
"go.problem.body":
|
||||
"私たちはOpenCodeの体験をできるだけ多くの人に届けることに注力しています。OpenCode Goは月額$10の低価格なサブスクリプションです。ゆとりある上限と、最も高性能なオープンソースモデルへの信頼できるアクセスを提供します。",
|
||||
"私たちはOpenCodeの体験をできるだけ多くの人に届けることに注力しています。OpenCode Goは低価格のサブスクリプションで、最初の月は$5、その後は$10/月です。ゆとりある上限と、最も高性能なオープンソースモデルへの信頼できるアクセスを提供します。",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "低価格なサブスクリプション料金",
|
||||
"go.problem.item2": "十分な制限と安定したアクセス",
|
||||
"go.problem.item3": "できるだけ多くのプログラマーのために構築",
|
||||
"go.problem.item4": "エージェント型コーディング向けにテストされた厳選モデルラインナップ",
|
||||
"go.how.title": "Goの仕組み",
|
||||
"go.how.body": "Goは月額$10です。OpenCodeまたは任意のエージェントで使えます。",
|
||||
"go.how.body": "Goは最初の月$5、その後$10/月で始まります。OpenCodeまたは任意のエージェントで使えます。",
|
||||
"go.how.step1.title": "アカウントを作成",
|
||||
"go.how.step1.beforeLink": "",
|
||||
"go.how.step1.link": "セットアップ手順はこちら",
|
||||
"go.how.step2.title": "Goを購読する",
|
||||
"go.how.step2.link": "月額$10",
|
||||
"go.how.step2.afterLink": "ゆとりある上限付き",
|
||||
"go.how.step2.link": "最初の月$5",
|
||||
"go.how.step2.afterLink": "その後$10/月、ゆとりある上限付き",
|
||||
"go.how.step3.title": "コーディングを開始",
|
||||
"go.how.step3.body": "オープンソースモデルへの安定したアクセスで",
|
||||
"go.privacy.title": "あなたのプライバシーは私たちにとって重要です",
|
||||
@@ -327,11 +328,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go には、十分な利用上限と安定したアクセスを備えた、以下のモデルが含まれます。",
|
||||
"go.faq.q3": "GoはZenと同じですか?",
|
||||
"go.faq.a3":
|
||||
"いいえ。Zenは従量課金制ですが、Goは月額$10で、厳選されたモデルラインナップへのゆとりある上限と安定したアクセスを提供します。",
|
||||
"いいえ。Zenは従量課金制ですが、Goは最初の月$5、その後$10/月で、厳選されたモデルラインナップへのゆとりある上限と安定したアクセスを提供します。",
|
||||
"go.faq.q4": "Goの料金は?",
|
||||
"go.faq.a4.p1.beforePricing": "Goは",
|
||||
"go.faq.a4.p1.pricingLink": "月額$10",
|
||||
"go.faq.a4.p1.afterPricing": "ゆとりある上限付き。",
|
||||
"go.faq.a4.p1.pricingLink": "最初の月$5",
|
||||
"go.faq.a4.p1.afterPricing": "その後$10/月、ゆとりある上限付き。",
|
||||
"go.faq.a4.p2.beforeAccount": "管理画面:",
|
||||
"go.faq.a4.p2.accountLink": "アカウント",
|
||||
"go.faq.a4.p3": "いつでもキャンセル可能です。",
|
||||
@@ -677,8 +678,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"このワークスペースの別のメンバーが既に OpenCode Go を購読しています。ワークスペースにつき1人のメンバーのみが購読できます。",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Goは{{price}}で、人気の高いオープンコーディングモデルへの安定したアクセスと余裕のある利用枠を提供します。",
|
||||
"workspace.lite.promo.price": "$10/月",
|
||||
"OpenCode Goは{{price}}で始まり、その後は$10/月で、人気の高いオープンコーディングモデルへの安定したアクセスと余裕のある利用枠を提供します。",
|
||||
"workspace.lite.promo.price": "初月$5",
|
||||
"workspace.lite.promo.modelsTitle": "含まれるもの",
|
||||
"workspace.lite.promo.footer":
|
||||
"このプランは主に海外のユーザー向けに設計されており、世界中から安定してご利用いただけます。料金と利用上限は、初期の利用状況やフィードバックを踏まえて変更される場合があります。",
|
||||
|
||||
@@ -252,7 +252,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | 모두를 위한 저비용 코딩 모델",
|
||||
"go.banner.text": "DeepSeek V4 Flash 사용 한도가 한시적으로 2배 확대됩니다",
|
||||
"go.meta.description":
|
||||
"Go는 월 $10이며, 넉넉한 사용 한도와 주요 코딩 모델에 대한 안정적인 액세스를 제공합니다.",
|
||||
"Go는 첫 달 $5, 이후 $10/월로 시작하며, 넉넉한 사용 한도와 주요 코딩 모델에 대한 안정적인 액세스를 제공합니다.",
|
||||
"go.hero.title": "모두를 위한 저비용 코딩 모델",
|
||||
"go.hero.body":
|
||||
"Go는 전 세계 프로그래머들에게 에이전트 코딩을 제공합니다. 가장 유능한 오픈 소스 모델에 대한 넉넉한 한도와 안정적인 액세스를 제공하므로, 비용이나 가용성 걱정 없이 강력한 에이전트로 빌드할 수 있습니다.",
|
||||
@@ -261,8 +261,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Go 구독하기",
|
||||
"go.cta.price": "$10/월",
|
||||
"go.cta.promo": "첫 달 $5",
|
||||
"go.pricing.body":
|
||||
"어떤 에이전트와도 사용할 수 있습니다. 월 $10. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.",
|
||||
"어떤 에이전트와도 사용할 수 있습니다. 첫 달 $5, 이후 $10/월. 필요하면 크레딧을 충전하세요. 언제든지 취소할 수 있습니다.",
|
||||
"go.graph.free": "무료",
|
||||
"go.graph.freePill": "Big Pickle 및 무료 모델",
|
||||
"go.graph.go": "Go",
|
||||
@@ -294,20 +295,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "아직 Nvidia에 있었으면 좋았을 텐데요.",
|
||||
"go.problem.title": "Go는 어떤 문제를 해결하나요?",
|
||||
"go.problem.body":
|
||||
"우리는 가능한 많은 사람들에게 OpenCode 경험을 제공하는 데 집중하고 있습니다. OpenCode Go는 월 $10의 저렴한 구독 서비스입니다. 넉넉한 한도와 가장 뛰어난 오픈 소스 모델에 대한 안정적인 액세스를 제공합니다.",
|
||||
"우리는 가능한 많은 사람들에게 OpenCode 경험을 제공하는 데 집중하고 있습니다. OpenCode Go는 저렴한 구독 서비스로, 첫 달 $5, 이후 $10/월입니다. 넉넉한 한도와 가장 뛰어난 오픈 소스 모델에 대한 안정적인 액세스를 제공합니다.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "저렴한 구독 가격",
|
||||
"go.problem.item2": "넉넉한 한도와 안정적인 액세스",
|
||||
"go.problem.item3": "가능한 한 많은 프로그래머를 위해 제작됨",
|
||||
"go.problem.item4": "에이전트 코딩용으로 테스트된 엄선된 모델 라인업",
|
||||
"go.how.title": "Go 작동 방식",
|
||||
"go.how.body": "Go는 월 $10입니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.",
|
||||
"go.how.body": "Go는 첫 달 $5, 이후 $10/월로 시작합니다. OpenCode 또는 어떤 에이전트와도 함께 사용할 수 있습니다.",
|
||||
"go.how.step1.title": "계정 생성",
|
||||
"go.how.step1.beforeLink": "",
|
||||
"go.how.step1.link": "설정 지침을 따르세요",
|
||||
"go.how.step2.title": "Go 구독",
|
||||
"go.how.step2.link": "월 $10",
|
||||
"go.how.step2.afterLink": "넉넉한 한도 포함",
|
||||
"go.how.step2.link": "첫 달 $5",
|
||||
"go.how.step2.afterLink": "이후 $10/월, 넉넉한 한도 포함",
|
||||
"go.how.step3.title": "코딩 시작",
|
||||
"go.how.step3.body": "오픈 소스 모델에 대한 안정적인 액세스와 함께",
|
||||
"go.privacy.title": "귀하의 프라이버시는 우리에게 중요합니다",
|
||||
@@ -323,11 +324,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go에는 넉넉한 한도와 안정적인 액세스를 제공하는 아래 모델이 포함됩니다.",
|
||||
"go.faq.q3": "Go는 Zen과 같은가요?",
|
||||
"go.faq.a3":
|
||||
"아니요. Zen은 종량제인 반면, Go는 월 $10이며, 엄선된 모델 라인업에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.",
|
||||
"아니요. Zen은 종량제인 반면, Go는 첫 달 $5, 이후 $10/월로 시작하며, 엄선된 모델 라인업에 대한 넉넉한 한도와 안정적인 액세스를 제공합니다.",
|
||||
"go.faq.q4": "Go 비용은 얼마인가요?",
|
||||
"go.faq.a4.p1.beforePricing": "Go 비용은",
|
||||
"go.faq.a4.p1.pricingLink": "월 $10",
|
||||
"go.faq.a4.p1.afterPricing": "넉넉한 한도 포함.",
|
||||
"go.faq.a4.p1.pricingLink": "첫 달 $5",
|
||||
"go.faq.a4.p1.afterPricing": "이후 $10/월, 넉넉한 한도 포함.",
|
||||
"go.faq.a4.p2.beforeAccount": "구독 관리는 다음에서 가능합니다:",
|
||||
"go.faq.a4.p2.accountLink": "계정",
|
||||
"go.faq.a4.p3": "언제든지 취소할 수 있습니다.",
|
||||
@@ -669,8 +670,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"이 워크스페이스의 다른 멤버가 이미 OpenCode Go를 구독 중입니다. 워크스페이스당 한 명의 멤버만 구독할 수 있습니다.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go는 {{price}}로 넉넉한 사용량 한도와 함께 인기 있는 오픈 코딩 모델에 대한 안정적인 액세스를 제공합니다.",
|
||||
"workspace.lite.promo.price": "$10/월",
|
||||
"OpenCode Go는 {{price}}부터 시작하며, 이후 $10/월로 넉넉한 사용량 한도와 함께 인기 있는 오픈 코딩 모델에 대한 안정적인 액세스를 제공합니다.",
|
||||
"workspace.lite.promo.price": "첫 달 $5",
|
||||
"workspace.lite.promo.modelsTitle": "포함 내역",
|
||||
"workspace.lite.promo.footer":
|
||||
"이 플랜은 주로 해외 사용자를 위해 설계되었으며, 전 세계에서 안정적으로 이용할 수 있습니다. 초기 이용 현황과 피드백을 반영하는 과정에서 가격과 사용 한도가 변경될 수 있습니다.",
|
||||
|
||||
@@ -256,7 +256,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Rimelige kodemodeller for alle",
|
||||
"go.banner.text": "DeepSeek V4 Flash får 2x bruksgrense i en begrenset periode",
|
||||
"go.meta.description":
|
||||
"Go koster $10/måned, med sjenerøse bruksgrenser og pålitelig tilgang til ledende kodemodeller.",
|
||||
"Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse bruksgrenser og pålitelig tilgang til ledende kodemodeller.",
|
||||
"go.hero.title": "Rimelige kodemodeller for alle",
|
||||
"go.hero.body":
|
||||
"Go bringer agent-koding til programmerere over hele verden. Med rause grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene, kan du bygge med kraftige agenter uten å bekymre deg for kostnader eller tilgjengelighet.",
|
||||
@@ -265,8 +265,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Abonner på Go",
|
||||
"go.cta.price": "$10/måned",
|
||||
"go.cta.promo": "$5 første måned",
|
||||
"go.pricing.body":
|
||||
"Bruk med hvilken som helst agent. $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.",
|
||||
"Bruk med hvilken som helst agent. $5 første måned, deretter $10/måned. Fyll på kreditt ved behov. Avslutt når som helst.",
|
||||
"go.graph.free": "Gratis",
|
||||
"go.graph.freePill": "Big Pickle og gratis modeller",
|
||||
"go.graph.go": "Go",
|
||||
@@ -297,7 +298,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Jeg skulle ønske jeg fortsatt var hos Nvidia.",
|
||||
"go.problem.title": "Hvilket problem løser Go?",
|
||||
"go.problem.body":
|
||||
"Vi fokuserer på å bringe OpenCode-opplevelsen til så mange som mulig. OpenCode Go er et rimelig abonnement til $10/måned. Det gir sjenerøse grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene.",
|
||||
"Vi fokuserer på å bringe OpenCode-opplevelsen til så mange som mulig. OpenCode Go er et rimelig abonnement: $5 for den første måneden, deretter $10/måned. Det gir sjenerøse grenser og pålitelig tilgang til de mest kapable åpen kildekode-modellene.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Rimelig abonnementspris",
|
||||
"go.problem.item2": "Rause grenser og pålitelig tilgang",
|
||||
@@ -305,13 +306,13 @@ export const dict = {
|
||||
"go.problem.item4": "Et kuratert modellutvalg testet for agent-koding",
|
||||
"go.how.title": "Hvordan Go fungerer",
|
||||
"go.how.body":
|
||||
"Go koster $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.",
|
||||
"Go starter på $5 for den første måneden, deretter $10/måned. Du kan bruke det med OpenCode eller hvilken som helst agent.",
|
||||
"go.how.step1.title": "Opprett en konto",
|
||||
"go.how.step1.beforeLink": "følg",
|
||||
"go.how.step1.link": "oppsettsinstruksjonene",
|
||||
"go.how.step2.title": "Abonner på Go",
|
||||
"go.how.step2.link": "$10/måned",
|
||||
"go.how.step2.afterLink": "med sjenerøse grenser",
|
||||
"go.how.step2.link": "$5 første måned",
|
||||
"go.how.step2.afterLink": "deretter $10/måned med sjenerøse grenser",
|
||||
"go.how.step3.title": "Begynn å kode",
|
||||
"go.how.step3.body": "med pålitelig tilgang til åpen kildekode-modeller",
|
||||
"go.privacy.title": "Personvernet ditt er viktig for oss",
|
||||
@@ -328,11 +329,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go inkluderer modellene nedenfor, med høye grenser og pålitelig tilgang.",
|
||||
"go.faq.q3": "Er Go det samme som Zen?",
|
||||
"go.faq.a3":
|
||||
"Nei. Zen er betaling etter bruk, mens Go koster $10/måned, med sjenerøse grenser og pålitelig tilgang til et kuratert modellutvalg.",
|
||||
"Nei. Zen er betaling etter bruk, mens Go starter på $5 for den første måneden, deretter $10/måned, med sjenerøse grenser og pålitelig tilgang til et kuratert modellutvalg.",
|
||||
"go.faq.q4": "Hva koster Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go koster",
|
||||
"go.faq.a4.p1.pricingLink": "$10/måned",
|
||||
"go.faq.a4.p1.afterPricing": "med sjenerøse grenser.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 første måned",
|
||||
"go.faq.a4.p1.afterPricing": "deretter $10/måned med sjenerøse grenser.",
|
||||
"go.faq.a4.p2.beforeAccount": "Du kan administrere abonnementet ditt i din",
|
||||
"go.faq.a4.p2.accountLink": "konto",
|
||||
"go.faq.a4.p3": "Avslutt når som helst.",
|
||||
@@ -679,8 +680,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Et annet medlem i dette arbeidsområdet abonnerer allerede på OpenCode Go. Kun ett medlem per arbeidsområde kan abonnere.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go koster {{price}} og gir pålitelig tilgang til populære åpne kodingsmodeller med sjenerøse bruksgrenser.",
|
||||
"workspace.lite.promo.price": "$10/måned",
|
||||
"OpenCode Go starter på {{price}}, deretter $10/måned, og gir pålitelig tilgang til populære åpne kodingsmodeller med sjenerøse bruksgrenser.",
|
||||
"workspace.lite.promo.price": "$5 for den første måneden",
|
||||
"workspace.lite.promo.modelsTitle": "Hva som er inkludert",
|
||||
"workspace.lite.promo.footer":
|
||||
"Planen er primært utviklet for internasjonale brukere og gir stabil global tilgang. Priser og bruksgrenser kan endres etter hvert som vi lærer av tidlig bruk og tilbakemeldinger.",
|
||||
|
||||
@@ -257,7 +257,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Niskokosztowe modele do kodowania dla każdego",
|
||||
"go.banner.text": "DeepSeek V4 Flash oferuje 2x wyższe limity użycia przez ograniczony czas",
|
||||
"go.meta.description":
|
||||
"Go kosztuje $10/miesiąc, oferując hojne limity użycia i niezawodny dostęp do wiodących modeli do kodowania.",
|
||||
"Go kosztuje $5 za pierwszy miesiąc, a następnie $10/miesiąc, oferując hojne limity użycia i niezawodny dostęp do wiodących modeli do kodowania.",
|
||||
"go.hero.title": "Niskokosztowe modele do kodowania dla każdego",
|
||||
"go.hero.body":
|
||||
"Go udostępnia programowanie z agentami programistom na całym świecie. Oferuje hojne limity i niezawodny dostęp do najzdolniejszych modeli open source, dzięki czemu możesz budować za pomocą potężnych agentów, nie martwiąc się o koszty czy dostępność.",
|
||||
@@ -266,8 +266,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Zasubskrybuj Go",
|
||||
"go.cta.price": "$10/miesiąc",
|
||||
"go.cta.promo": "$5 pierwszy miesiąc",
|
||||
"go.pricing.body":
|
||||
"Używaj z dowolnym agentem. $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.",
|
||||
"Używaj z dowolnym agentem. $5 za pierwszy miesiąc, potem $10/miesiąc. Doładuj konto w razie potrzeby. Anuluj w dowolnym momencie.",
|
||||
"go.graph.free": "Darmowe",
|
||||
"go.graph.freePill": "Big Pickle i darmowe modele",
|
||||
"go.graph.go": "Go",
|
||||
@@ -298,7 +299,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Chciałbym wciąż być w Nvidia.",
|
||||
"go.problem.title": "Jaki problem rozwiązuje Go?",
|
||||
"go.problem.body":
|
||||
"Skupiamy się na udostępnieniu doświadczenia OpenCode jak największej liczbie osób. OpenCode Go to tania subskrypcja za $10/miesiąc. Zapewnia hojne limity i niezawodny dostęp do najbardziej wydajnych modeli open source.",
|
||||
"Skupiamy się na udostępnieniu doświadczenia OpenCode jak największej liczbie osób. OpenCode Go to tania subskrypcja: $5 za pierwszy miesiąc, potem $10/miesiąc. Zapewnia hojne limity i niezawodny dostęp do najbardziej wydajnych modeli open source.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Niskokosztowa cena subskrypcji",
|
||||
"go.problem.item2": "Hojne limity i niezawodny dostęp",
|
||||
@@ -306,13 +307,13 @@ export const dict = {
|
||||
"go.problem.item4": "Starannie dobrany zestaw modeli przetestowanych pod kątem kodowania z agentami",
|
||||
"go.how.title": "Jak działa Go",
|
||||
"go.how.body":
|
||||
"Go kosztuje $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.",
|
||||
"Go zaczyna się od $5 za pierwszy miesiąc, potem $10/miesiąc. Możesz go używać z OpenCode lub dowolnym agentem.",
|
||||
"go.how.step1.title": "Załóż konto",
|
||||
"go.how.step1.beforeLink": "postępuj zgodnie z",
|
||||
"go.how.step1.link": "instrukcją konfiguracji",
|
||||
"go.how.step2.title": "Zasubskrybuj Go",
|
||||
"go.how.step2.link": "$10/miesiąc",
|
||||
"go.how.step2.afterLink": "z hojnymi limitami",
|
||||
"go.how.step2.link": "$5 za pierwszy miesiąc",
|
||||
"go.how.step2.afterLink": "potem $10/miesiąc z hojnymi limitami",
|
||||
"go.how.step3.title": "Zacznij kodować",
|
||||
"go.how.step3.body": "z niezawodnym dostępem do modeli open source",
|
||||
"go.privacy.title": "Twoja prywatność jest dla nas ważna",
|
||||
@@ -329,11 +330,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go obejmuje poniższe modele z wysokimi limitami i niezawodnym dostępem.",
|
||||
"go.faq.q3": "Czy Go to to samo co Zen?",
|
||||
"go.faq.a3":
|
||||
"Nie. Zen działa w modelu płatności za użycie, natomiast Go kosztuje $10/miesiąc, oferując hojne limity i niezawodny dostęp do starannie dobranego zestawu modeli.",
|
||||
"Nie. Zen działa w modelu płatności za użycie, natomiast Go kosztuje $5 za pierwszy miesiąc, a następnie $10/miesiąc, oferując hojne limity i niezawodny dostęp do starannie dobranego zestawu modeli.",
|
||||
"go.faq.q4": "Ile kosztuje Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go kosztuje",
|
||||
"go.faq.a4.p1.pricingLink": "$10/miesiąc",
|
||||
"go.faq.a4.p1.afterPricing": "z hojnymi limitami.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 za pierwszy miesiąc",
|
||||
"go.faq.a4.p1.afterPricing": "potem $10/miesiąc z hojnymi limitami.",
|
||||
"go.faq.a4.p2.beforeAccount": "Możesz zarządzać subskrypcją na swoim",
|
||||
"go.faq.a4.p2.accountLink": "koncie",
|
||||
"go.faq.a4.p3": "Anuluj w dowolnym momencie.",
|
||||
@@ -679,8 +680,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Inny członek tego obszaru roboczego już subskrybuje OpenCode Go. Tylko jeden członek na obszar roboczy może subskrybować.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go kosztuje {{price}} i zapewnia niezawodny dostęp do popularnych otwartych modeli kodowania z hojnymi limitami użycia.",
|
||||
"workspace.lite.promo.price": "$10/miesiąc",
|
||||
"OpenCode Go zaczyna się od {{price}}, potem $10/miesiąc, i zapewnia niezawodny dostęp do popularnych otwartych modeli kodowania z hojnymi limitami użycia.",
|
||||
"workspace.lite.promo.price": "$5 za pierwszy miesiąc",
|
||||
"workspace.lite.promo.modelsTitle": "Co zawiera",
|
||||
"workspace.lite.promo.footer":
|
||||
"Plan został opracowany przede wszystkim z myślą o użytkownikach z całego świata i zapewnia stabilny globalny dostęp. Ceny i limity użycia mogą ulec zmianie w miarę zdobywania doświadczeń na podstawie początkowego korzystania z usługi i otrzymywanych opinii.",
|
||||
|
||||
@@ -260,7 +260,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Недорогие модели для кодинга для всех",
|
||||
"go.banner.text": "DeepSeek V4 Flash получает 2x лимиты использования на ограниченное время",
|
||||
"go.meta.description":
|
||||
"Go стоит $10/месяц и предлагает щедрые лимиты использования и надежный доступ к ведущим моделям для кодинга.",
|
||||
"Go стоит $5 за первый месяц, затем $10/месяц и предлагает щедрые лимиты использования и надежный доступ к ведущим моделям для кодинга.",
|
||||
"go.hero.title": "Недорогие модели для кодинга для всех",
|
||||
"go.hero.body":
|
||||
"Go открывает доступ к агентам-программистам разработчикам по всему миру. Предлагая щедрые лимиты и надежный доступ к наиболее способным моделям с открытым исходным кодом, вы можете создавать проекты с мощными агентами, не беспокоясь о затратах или доступности.",
|
||||
@@ -269,8 +269,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Подписаться на Go",
|
||||
"go.cta.price": "$10/месяц",
|
||||
"go.cta.promo": "$5 первый месяц",
|
||||
"go.pricing.body":
|
||||
"Используйте с любым агентом. $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.",
|
||||
"Используйте с любым агентом. $5 за первый месяц, затем $10/месяц. Пополняйте баланс при необходимости. Отменить можно в любое время.",
|
||||
"go.graph.free": "Бесплатно",
|
||||
"go.graph.freePill": "Big Pickle и бесплатные модели",
|
||||
"go.graph.go": "Go",
|
||||
@@ -302,7 +303,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Жаль, что я больше не в Nvidia.",
|
||||
"go.problem.title": "Какую проблему решает Go?",
|
||||
"go.problem.body":
|
||||
"Мы стремимся сделать OpenCode доступным для как можно большего числа людей. OpenCode Go - это недорогая подписка за $10/месяц. Она предоставляет щедрые лимиты и надежный доступ к самым мощным моделям с открытым исходным кодом.",
|
||||
"Мы стремимся сделать OpenCode доступным для как можно большего числа людей. OpenCode Go - это недорогая подписка: $5 за первый месяц, затем $10/месяц. Она предоставляет щедрые лимиты и надежный доступ к самым мощным моделям с открытым исходным кодом.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Недорогая подписка",
|
||||
"go.problem.item2": "Щедрые лимиты и надежный доступ",
|
||||
@@ -310,13 +311,13 @@ export const dict = {
|
||||
"go.problem.item4": "Отобранные модели, протестированные для агентного программирования",
|
||||
"go.how.title": "Как работает Go",
|
||||
"go.how.body":
|
||||
"Go стоит $10/месяц. Вы можете использовать его с OpenCode или любым агентом.",
|
||||
"Go начинается с $5 за первый месяц, затем $10/месяц. Вы можете использовать его с OpenCode или любым агентом.",
|
||||
"go.how.step1.title": "Создайте аккаунт",
|
||||
"go.how.step1.beforeLink": "следуйте",
|
||||
"go.how.step1.link": "инструкциям по настройке",
|
||||
"go.how.step2.title": "Подпишитесь на Go",
|
||||
"go.how.step2.link": "$10/месяц",
|
||||
"go.how.step2.afterLink": "с щедрыми лимитами",
|
||||
"go.how.step2.link": "$5 за первый месяц",
|
||||
"go.how.step2.afterLink": "затем $10/месяц с щедрыми лимитами",
|
||||
"go.how.step3.title": "Начните кодить",
|
||||
"go.how.step3.body": "с надежным доступом к open-source моделям",
|
||||
"go.privacy.title": "Ваша приватность важна для нас",
|
||||
@@ -333,11 +334,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go включает перечисленные ниже модели с щедрыми лимитами и надежным доступом.",
|
||||
"go.faq.q3": "Go — это то же самое, что и Zen?",
|
||||
"go.faq.a3":
|
||||
"Нет. Zen оплачивается по мере использования, а Go стоит $10/месяц и предлагает щедрые лимиты и надежный доступ к отобранным моделям.",
|
||||
"Нет. Zen оплачивается по мере использования, а Go стоит $5 за первый месяц, затем $10/месяц и предлагает щедрые лимиты и надежный доступ к отобранным моделям.",
|
||||
"go.faq.q4": "Сколько стоит Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go стоит",
|
||||
"go.faq.a4.p1.pricingLink": "$10/месяц",
|
||||
"go.faq.a4.p1.afterPricing": "с щедрыми лимитами.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 за первый месяц",
|
||||
"go.faq.a4.p1.afterPricing": "затем $10/месяц с щедрыми лимитами.",
|
||||
"go.faq.a4.p2.beforeAccount": "Вы можете управлять подпиской в своем",
|
||||
"go.faq.a4.p2.accountLink": "аккаунте",
|
||||
"go.faq.a4.p3": "Отмена в любое время.",
|
||||
@@ -686,8 +687,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Другой участник в этом рабочем пространстве уже подписан на OpenCode Go. Только один участник в рабочем пространстве может оформить подписку.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go стоит {{price}} и предоставляет надежный доступ к популярным открытым моделям кодирования с щедрыми лимитами использования.",
|
||||
"workspace.lite.promo.price": "$10/месяц",
|
||||
"OpenCode Go начинается с {{price}}, затем $10/месяц и предоставляет надежный доступ к популярным открытым моделям кодирования с щедрыми лимитами использования.",
|
||||
"workspace.lite.promo.price": "$5 за первый месяц",
|
||||
"workspace.lite.promo.modelsTitle": "Что включено",
|
||||
"workspace.lite.promo.footer":
|
||||
"План предназначен в первую очередь для пользователей по всему миру и обеспечивает стабильный глобальный доступ. Цены и лимиты использования могут меняться по мере изучения первых результатов использования и отзывов.",
|
||||
|
||||
@@ -255,7 +255,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||
"go.banner.text": "DeepSeek V4 Flash เพิ่มโควตาการใช้งานเป็น 2 เท่าในช่วงเวลาจำกัด",
|
||||
"go.meta.description":
|
||||
"Go มีราคา $10/เดือน พร้อมขีดจำกัดการใช้งานที่เอื้อเฟื้อและการเข้าถึงโมเดลเขียนโค้ดชั้นนำอย่างเชื่อถือได้",
|
||||
"Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดการใช้งานที่เอื้อเฟื้อและการเข้าถึงโมเดลเขียนโค้ดชั้นนำอย่างเชื่อถือได้",
|
||||
"go.hero.title": "โมเดลเขียนโค้ดราคาประหยัดสำหรับทุกคน",
|
||||
"go.hero.body":
|
||||
"Go นำการเขียนโค้ดแบบเอเจนต์มาสู่นักเขียนโปรแกรมทั่วโลก เสนอขีดจำกัดที่กว้างขวางและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดได้อย่างน่าเชื่อถือ เพื่อให้คุณสามารถสร้างสรรค์ด้วยเอเจนต์ที่ทรงพลังโดยไม่ต้องกังวลเรื่องค่าใช้จ่ายหรือความพร้อมใช้งาน",
|
||||
@@ -264,7 +264,8 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "สมัครสมาชิก Go",
|
||||
"go.cta.price": "$10/เดือน",
|
||||
"go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา",
|
||||
"go.cta.promo": "$5 เดือนแรก",
|
||||
"go.pricing.body": "ใช้กับเอเจนต์ใดก็ได้ $5 ในเดือนแรก จากนั้น $10/เดือน เติมเครดิตหากจำเป็น ยกเลิกได้ตลอดเวลา",
|
||||
"go.graph.free": "ฟรี",
|
||||
"go.graph.freePill": "Big Pickle และโมเดลฟรี",
|
||||
"go.graph.go": "Go",
|
||||
@@ -295,20 +296,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "ผมหวังว่าผมจะยังอยู่ที่ Nvidia",
|
||||
"go.problem.title": "Go แก้ปัญหาอะไร?",
|
||||
"go.problem.body":
|
||||
"เรามุ่งมั่นที่จะนำประสบการณ์ OpenCode ไปสู่ผู้คนให้ได้มากที่สุด OpenCode Go เป็นการสมัครสมาชิกราคาประหยัด $10/เดือน โดยมอบขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดอย่างเชื่อถือได้",
|
||||
"เรามุ่งมั่นที่จะนำประสบการณ์ OpenCode ไปสู่ผู้คนให้ได้มากที่สุด OpenCode Go เป็นการสมัครสมาชิกราคาประหยัด: $5 สำหรับเดือนแรก จากนั้น $10/เดือน โดยมอบขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงโมเดลโอเพนซอร์สที่มีความสามารถสูงสุดอย่างเชื่อถือได้",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "ราคาการสมัครสมาชิกที่ต่ำ",
|
||||
"go.problem.item2": "ขีดจำกัดที่กว้างขวางและการเข้าถึงที่เชื่อถือได้",
|
||||
"go.problem.item3": "สร้างขึ้นเพื่อโปรแกรมเมอร์จำนวนมากที่สุดเท่าที่จะเป็นไปได้",
|
||||
"go.problem.item4": "ชุดโมเดลที่คัดสรรและผ่านการทดสอบสำหรับการเขียนโค้ดแบบเอเจนต์",
|
||||
"go.how.title": "Go ทำงานอย่างไร",
|
||||
"go.how.body": "Go มีราคา $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้",
|
||||
"go.how.body": "Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน คุณสามารถใช้กับ OpenCode หรือเอเจนต์ใดก็ได้",
|
||||
"go.how.step1.title": "สร้างบัญชี",
|
||||
"go.how.step1.beforeLink": "ทำตาม",
|
||||
"go.how.step1.link": "คำแนะนำการตั้งค่า",
|
||||
"go.how.step2.title": "สมัครสมาชิก Go",
|
||||
"go.how.step2.link": "$10/เดือน",
|
||||
"go.how.step2.afterLink": "พร้อมขีดจำกัดที่เอื้อเฟื้อ",
|
||||
"go.how.step2.link": "$5 เดือนแรก",
|
||||
"go.how.step2.afterLink": "จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อ",
|
||||
"go.how.step3.title": "เริ่มเขียนโค้ด",
|
||||
"go.how.step3.body": "ด้วยการเข้าถึงโมเดลโอเพนซอร์สที่เชื่อถือได้",
|
||||
"go.privacy.title": "ความเป็นส่วนตัวของคุณสำคัญสำหรับเรา",
|
||||
@@ -325,11 +326,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go รวมโมเดลด้านล่างนี้ พร้อมขีดจำกัดที่มากและการเข้าถึงที่เชื่อถือได้",
|
||||
"go.faq.q3": "Go เหมือนกับ Zen หรือไม่?",
|
||||
"go.faq.a3":
|
||||
"ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ขณะที่ Go มีราคา $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงชุดโมเดลที่คัดสรรอย่างเชื่อถือได้",
|
||||
"ไม่ Zen เป็นแบบจ่ายตามการใช้งาน ขณะที่ Go เริ่มต้นที่ $5 สำหรับเดือนแรก จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อและการเข้าถึงชุดโมเดลที่คัดสรรอย่างเชื่อถือได้",
|
||||
"go.faq.q4": "Go ราคาเท่าไหร่?",
|
||||
"go.faq.a4.p1.beforePricing": "Go ราคา",
|
||||
"go.faq.a4.p1.pricingLink": "$10/เดือน",
|
||||
"go.faq.a4.p1.afterPricing": "พร้อมขีดจำกัดที่เอื้อเฟื้อ",
|
||||
"go.faq.a4.p1.pricingLink": "$5 เดือนแรก",
|
||||
"go.faq.a4.p1.afterPricing": "จากนั้น $10/เดือน พร้อมขีดจำกัดที่เอื้อเฟื้อ",
|
||||
"go.faq.a4.p2.beforeAccount": "คุณสามารถจัดการการสมัครสมาชิกของคุณได้ใน",
|
||||
"go.faq.a4.p2.accountLink": "บัญชีของคุณ",
|
||||
"go.faq.a4.p3": "ยกเลิกได้ตลอดเวลา",
|
||||
@@ -674,8 +675,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"สมาชิกคนอื่นใน Workspace นี้ได้สมัคร OpenCode Go แล้ว สามารถสมัครได้เพียงหนึ่งคนต่อหนึ่ง Workspace เท่านั้น",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go ราคา {{price}} และมอบการเข้าถึงโมเดลการเขียนโค้ดแบบเปิดยอดนิยมอย่างเสถียรพร้อมขีดจำกัดการใช้งานที่ให้มาอย่างเหลือเฟือ",
|
||||
"workspace.lite.promo.price": "$10/เดือน",
|
||||
"OpenCode Go เริ่มต้นที่ {{price}} จากนั้น $10/เดือน และมอบการเข้าถึงโมเดลการเขียนโค้ดแบบเปิดยอดนิยมอย่างเสถียรพร้อมขีดจำกัดการใช้งานที่ให้มาอย่างเหลือเฟือ",
|
||||
"workspace.lite.promo.price": "$5 สำหรับเดือนแรก",
|
||||
"workspace.lite.promo.modelsTitle": "สิ่งที่รวมอยู่ด้วย",
|
||||
"workspace.lite.promo.footer":
|
||||
"แผนนี้ออกแบบมาสำหรับผู้ใช้งานต่างประเทศเป็นหลักและให้การเข้าถึงที่เสถียรทั่วโลก ราคาและขีดจำกัดการใช้งานอาจเปลี่ยนแปลงได้ตามสิ่งที่เราเรียนรู้จากการใช้งานและข้อเสนอแนะในช่วงแรก",
|
||||
|
||||
@@ -258,7 +258,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Herkes için düşük maliyetli kodlama modelleri",
|
||||
"go.banner.text": "DeepSeek V4 Flash sınırlı bir süre için 2x kullanım limiti sunuyor",
|
||||
"go.meta.description":
|
||||
"Go ayda 10$'dır; cömert kullanım limitleri ve önde gelen kodlama modellerine güvenilir erişim sunar.",
|
||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar; cömert kullanım limitleri ve önde gelen kodlama modellerine güvenilir erişim sunar.",
|
||||
"go.hero.title": "Herkes için düşük maliyetli kodlama modelleri",
|
||||
"go.hero.body":
|
||||
"Go, dünya çapındaki programcılara ajan tabanlı kodlama getiriyor. En yetenekli açık kaynaklı modellere cömert limitler ve güvenilir erişim sunarak, maliyet veya erişilebilirlik konusunda endişelenmeden güçlü ajanlarla geliştirme yapmanızı sağlar.",
|
||||
@@ -267,8 +267,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Go'ya abone ol",
|
||||
"go.cta.price": "Ayda 10$",
|
||||
"go.cta.promo": "İlk ay $5",
|
||||
"go.pricing.body":
|
||||
"Herhangi bir ajanla kullanın. Ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.",
|
||||
"Herhangi bir ajanla kullanın. İlk ay $5, sonrasında ayda 10$. Gerekirse kredi yükleyin. İstediğiniz zaman iptal edin.",
|
||||
"go.graph.free": "Ücretsiz",
|
||||
"go.graph.freePill": "Big Pickle ve ücretsiz modeller",
|
||||
"go.graph.go": "Go",
|
||||
@@ -300,7 +301,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Keşke hala Nvidia'da olsaydım.",
|
||||
"go.problem.title": "Go hangi sorunu çözüyor?",
|
||||
"go.problem.body":
|
||||
"OpenCode deneyimini mümkün olduğunca çok kişiye ulaştırmaya odaklandık. OpenCode Go, ayda 10$ olan düşük maliyetli bir aboneliktir. Cömert limitler ve en yetenekli açık kaynak modellere güvenilir erişim sağlar.",
|
||||
"OpenCode deneyimini mümkün olduğunca çok kişiye ulaştırmaya odaklandık. OpenCode Go düşük maliyetli bir aboneliktir: İlk ay $5, sonrasında ayda 10$. Cömert limitler ve en yetenekli açık kaynak modellere güvenilir erişim sağlar.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Düşük maliyetli abonelik fiyatlandırması",
|
||||
"go.problem.item2": "Cömert limitler ve güvenilir erişim",
|
||||
@@ -308,13 +309,13 @@ export const dict = {
|
||||
"go.problem.item4": "Ajan tabanlı kodlama için test edilmiş, özenle seçilmiş model seçenekleri",
|
||||
"go.how.title": "Go nasıl çalışır?",
|
||||
"go.how.body":
|
||||
"Go ayda 10$'dır. OpenCode veya herhangi bir ajanla kullanabilirsiniz.",
|
||||
"Go ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar. OpenCode veya herhangi bir ajanla kullanabilirsiniz.",
|
||||
"go.how.step1.title": "Bir hesap oluşturun",
|
||||
"go.how.step1.beforeLink": "takip edin",
|
||||
"go.how.step1.link": "kurulum talimatları",
|
||||
"go.how.step2.title": "Go'ya abone olun",
|
||||
"go.how.step2.link": "Ayda 10$",
|
||||
"go.how.step2.afterLink": "cömert limitlerle",
|
||||
"go.how.step2.link": "İlk ay $5",
|
||||
"go.how.step2.afterLink": "sonrasında cömert limitlerle ayda 10$",
|
||||
"go.how.step3.title": "Kodlamaya başlayın",
|
||||
"go.how.step3.body": "açık kaynaklı modellere güvenilir erişimle",
|
||||
"go.privacy.title": "Gizliliğiniz bizim için önemlidir",
|
||||
@@ -331,11 +332,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go, aşağıda listelenen modelleri cömert limitler ve güvenilir erişimle sunar.",
|
||||
"go.faq.q3": "Go, Zen ile aynı mı?",
|
||||
"go.faq.a3":
|
||||
"Hayır. Zen kullandıkça öde modelidir; Go ise ayda 10$'dır ve özenle seçilmiş model seçeneklerine cömert limitlerle güvenilir erişim sunar.",
|
||||
"Hayır. Zen kullandıkça öde modelidir; Go ise ilk ay $5, sonrasında ayda 10$ fiyatıyla başlar ve özenle seçilmiş model seçeneklerine cömert limitlerle güvenilir erişim sunar.",
|
||||
"go.faq.q4": "Go ne kadar?",
|
||||
"go.faq.a4.p1.beforePricing": "Go'nun maliyeti",
|
||||
"go.faq.a4.p1.pricingLink": "ayda 10$",
|
||||
"go.faq.a4.p1.afterPricing": "cömert limitlerle.",
|
||||
"go.faq.a4.p1.pricingLink": "İlk ay $5",
|
||||
"go.faq.a4.p1.afterPricing": "sonrasında cömert limitlerle ayda 10$.",
|
||||
"go.faq.a4.p2.beforeAccount": "Aboneliğinizi",
|
||||
"go.faq.a4.p2.accountLink": "hesabınızdan",
|
||||
"go.faq.a4.p3": "yönetebilirsiniz. İstediğiniz zaman iptal edin.",
|
||||
@@ -682,8 +683,8 @@ export const dict = {
|
||||
"workspace.lite.other.message":
|
||||
"Bu çalışma alanındaki başka bir üye zaten OpenCode Go abonesi. Çalışma alanı başına yalnızca bir üye abone olabilir.",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go {{price}} fiyatıyla cömert kullanım limitleri ve popüler açık kodlama modellerine güvenilir erişim sağlar.",
|
||||
"workspace.lite.promo.price": "Ayda 10$",
|
||||
"OpenCode Go {{price}} fiyatından başlar, sonrasında ayda 10$ olur ve cömert kullanım limitleriyle popüler açık kodlama modellerine güvenilir erişim sağlar.",
|
||||
"workspace.lite.promo.price": "İlk ay $5",
|
||||
"workspace.lite.promo.modelsTitle": "Neler Dahil",
|
||||
"workspace.lite.promo.footer":
|
||||
"Plan öncelikle uluslararası kullanıcılar için tasarlanmıştır ve istikrarlı küresel erişim sağlar. Erken kullanım ve geri bildirimlerden öğrendiklerimiz doğrultusunda fiyatlandırma ve kullanım limitleri değişebilir.",
|
||||
|
||||
@@ -257,7 +257,7 @@ export const dict = {
|
||||
"go.title": "OpenCode Go | Недорогі моделі кодування для всіх",
|
||||
"go.banner.text": "DeepSeek V4 Flash отримує 2x ліміти використання протягом обмеженого часу",
|
||||
"go.meta.description":
|
||||
"Go коштує $10/місяць, зі щедрими лімітами використання та надійним доступом до провідних моделей для кодування.",
|
||||
"Go починається від $5 за перший місяць, потім $10/місяць, зі щедрими лімітами використання та надійним доступом до провідних моделей для кодування.",
|
||||
"go.hero.title": "Недорогі моделі кодування для всіх",
|
||||
"go.hero.body":
|
||||
"Go надає агентне програмування програмістам у всьому світі, пропонуючи щедрі ліміти та надійний доступ до найкращих моделей з відкритим кодом.",
|
||||
@@ -266,8 +266,9 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "Підписатися на Go",
|
||||
"go.cta.price": "$10/місяць",
|
||||
"go.cta.promo": "$5 перший місяць",
|
||||
"go.pricing.body":
|
||||
"Використовуйте з будь-яким агентом. $10/місяць. Поповнюйте за потреби. Скасуйте в будь-який час.",
|
||||
"Використовуйте з будь-яким агентом. $5 перший місяць, потім $10/місяць. Поповнюйте за потреби. Скасуйте в будь-який час.",
|
||||
"go.graph.free": "Безкоштовно",
|
||||
"go.graph.freePill": "Big Pickle та безкоштовні моделі",
|
||||
"go.graph.go": "Go",
|
||||
@@ -298,7 +299,7 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "Хотів би я досі бути в Nvidia.",
|
||||
"go.problem.title": "Яку проблему вирішує Go?",
|
||||
"go.problem.body":
|
||||
"Ми зосереджені на тому, щоб зробити досвід OpenCode доступним для якомога більшої кількості людей. OpenCode Go — це недорога підписка за $10/місяць. Вона надає щедрі ліміти та надійний доступ до найкращих моделей з відкритим кодом.",
|
||||
"Ми зосереджені на тому, щоб зробити досвід OpenCode доступним для якомога більшої кількості людей. OpenCode Go — це недорога підписка: $5 за перший місяць, потім $10/місяць. Вона надає щедрі ліміти та надійний доступ до найкращих моделей з відкритим кодом.",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "Недорога підписка",
|
||||
"go.problem.item2": "Щедрі ліміти та надійний доступ",
|
||||
@@ -306,13 +307,13 @@ export const dict = {
|
||||
"go.problem.item4": "Добірка моделей, протестованих для агентного кодування",
|
||||
"go.how.title": "Як працює Go",
|
||||
"go.how.body":
|
||||
"Go коштує $10/місяць. Використовуйте з OpenCode або будь-яким агентом.",
|
||||
"Go починається від $5 за перший місяць, потім $10/місяць. Використовуйте з OpenCode або будь-яким агентом.",
|
||||
"go.how.step1.title": "Створіть обліковий запис",
|
||||
"go.how.step1.beforeLink": "дотримуйтесь",
|
||||
"go.how.step1.link": "інструкцій з налаштування",
|
||||
"go.how.step2.title": "Підпишіться на Go",
|
||||
"go.how.step2.link": "$10/місяць",
|
||||
"go.how.step2.afterLink": "із щедрими лімітами",
|
||||
"go.how.step2.link": "$5 перший місяць",
|
||||
"go.how.step2.afterLink": "потім $10/місяць із щедрими лімітами",
|
||||
"go.how.step3.title": "Почніть кодувати",
|
||||
"go.how.step3.body": "з надійним доступом до моделей з відкритим кодом",
|
||||
"go.privacy.title": "Ваша конфіденційність важлива для нас",
|
||||
@@ -329,11 +330,11 @@ export const dict = {
|
||||
"go.faq.a2": "Go включає моделі, перелічені нижче, із щедрими лімітами та надійним доступом.",
|
||||
"go.faq.q3": "Чи Go те саме, що Zen?",
|
||||
"go.faq.a3":
|
||||
"Ні. Zen — це плата за використання, тоді як Go коштує $10/місяць, із щедрими лімітами та надійним доступом до добірки моделей.",
|
||||
"Ні. Zen — це плата за використання, тоді як Go починається від $5 за перший місяць, потім $10/місяць, із щедрими лімітами та надійним доступом до добірки моделей.",
|
||||
"go.faq.q4": "Скільки коштує Go?",
|
||||
"go.faq.a4.p1.beforePricing": "Go коштує",
|
||||
"go.faq.a4.p1.pricingLink": "$10/місяць",
|
||||
"go.faq.a4.p1.afterPricing": "із щедрими лімітами.",
|
||||
"go.faq.a4.p1.pricingLink": "$5 за перший місяць",
|
||||
"go.faq.a4.p1.afterPricing": "потім $10/місяць із щедрими лімітами.",
|
||||
"go.faq.a4.p2.beforeAccount": "Ви можете керувати підпискою в",
|
||||
"go.faq.a4.p2.accountLink": "обліковому записі",
|
||||
"go.faq.a4.p3": "Скасуйте в будь-який час.",
|
||||
@@ -675,8 +676,8 @@ export const dict = {
|
||||
"workspace.lite.black.message":
|
||||
"Ви вже підписані на OpenCode Black або в списку очікування. Спочатку скасуйте підписку, якщо хочете перейти на Go.",
|
||||
"workspace.lite.other.message": "Інший учасник цього робочого простору вже підписаний на OpenCode Go.",
|
||||
"workspace.lite.promo.description": "OpenCode Go коштує {{price}} і має щедрі ліміти.",
|
||||
"workspace.lite.promo.price": "$10/місяць",
|
||||
"workspace.lite.promo.description": "OpenCode Go починається від {{price}}, потім $10/місяць, із щедрими лімітами.",
|
||||
"workspace.lite.promo.price": "$5 за перший місяць",
|
||||
"workspace.lite.promo.modelsTitle": "Що включено",
|
||||
"workspace.lite.promo.footer":
|
||||
"План призначений насамперед для міжнародних користувачів і забезпечує стабільний глобальний доступ. Ціни та ліміти використання можуть змінюватися з урахуванням перших даних про використання та відгуків.",
|
||||
|
||||
@@ -245,7 +245,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | 人人可用的低成本编程模型",
|
||||
"go.banner.text": "DeepSeek V4 Flash 限时享受 2 倍使用额度",
|
||||
"go.meta.description": "Go 每月 $10,提供充裕的使用限额,并可可靠访问领先的编程模型。",
|
||||
"go.meta.description": "Go 首月 $5,之后 $10/月,提供充裕的使用限额,并可可靠访问领先的编程模型。",
|
||||
"go.hero.title": "人人可用的低成本编程模型",
|
||||
"go.hero.body":
|
||||
"Go 将代理编程带给全世界的程序员。提供充裕的限额和对最强大的开源模型的可靠访问,让您可以利用强大的代理进行构建,而无需担心成本或可用性。",
|
||||
@@ -254,7 +254,8 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "订阅 Go",
|
||||
"go.cta.price": "$10/月",
|
||||
"go.pricing.body": "可配合任何代理使用。每月 $10。如有需要可充值。随时取消。",
|
||||
"go.cta.promo": "首月 $5",
|
||||
"go.pricing.body": "可配合任何代理使用。首月 $5,之后 $10/月。如有需要可充值。随时取消。",
|
||||
"go.graph.free": "免费",
|
||||
"go.graph.freePill": "Big Pickle 和免费模型",
|
||||
"go.graph.go": "Go",
|
||||
@@ -285,20 +286,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "我希望我还在 Nvidia。",
|
||||
"go.problem.title": "Go 解决了什么问题?",
|
||||
"go.problem.body":
|
||||
"我们致力于将 OpenCode 体验带给尽可能多的人。OpenCode Go 是一款每月 $10 的低成本订阅服务。它提供充裕的额度,并让您能可靠地使用最强大的开源模型。",
|
||||
"我们致力于将 OpenCode 体验带给尽可能多的人。OpenCode Go 是一款低成本订阅服务:首月 $5,之后 $10/月。它提供充裕的额度,并让您能可靠地使用最强大的开源模型。",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "低成本订阅定价",
|
||||
"go.problem.item2": "充裕的限额和可靠的访问",
|
||||
"go.problem.item3": "为尽可能多的程序员打造",
|
||||
"go.problem.item4": "经过代理编程测试的精选模型阵容",
|
||||
"go.how.title": "Go 如何工作",
|
||||
"go.how.body": "Go 每月 $10。您可以将其与 OpenCode 或任何代理搭配使用。",
|
||||
"go.how.body": "Go 起价为首月 $5,之后 $10/月。您可以将其与 OpenCode 或任何代理搭配使用。",
|
||||
"go.how.step1.title": "创建账户",
|
||||
"go.how.step1.beforeLink": "遵循",
|
||||
"go.how.step1.link": "设置说明",
|
||||
"go.how.step2.title": "订阅 Go",
|
||||
"go.how.step2.link": "每月 $10",
|
||||
"go.how.step2.afterLink": "额度充裕",
|
||||
"go.how.step2.link": "首月 $5",
|
||||
"go.how.step2.afterLink": "之后 $10/月,额度充裕",
|
||||
"go.how.step3.title": "开始编程",
|
||||
"go.how.step3.body": "可靠访问开源模型",
|
||||
"go.privacy.title": "您的隐私对我们很重要",
|
||||
@@ -311,11 +312,11 @@ export const dict = {
|
||||
"go.faq.q2": "Go 包含哪些模型?",
|
||||
"go.faq.a2": "Go 包含下方列出的模型,提供充足的限额和可靠的访问。",
|
||||
"go.faq.q3": "Go 和 Zen 一样吗?",
|
||||
"go.faq.a3": "不。Zen 是按量付费,而 Go 每月 $10,提供充裕的限额,并可可靠访问精选模型阵容。",
|
||||
"go.faq.a3": "不。Zen 是按量付费,而 Go 首月 $5,之后 $10/月,提供充裕的限额,并可可靠访问精选模型阵容。",
|
||||
"go.faq.q4": "Go 多少钱?",
|
||||
"go.faq.a4.p1.beforePricing": "Go 费用为",
|
||||
"go.faq.a4.p1.pricingLink": "每月 $10",
|
||||
"go.faq.a4.p1.afterPricing": "额度充裕。",
|
||||
"go.faq.a4.p1.pricingLink": "首月 $5",
|
||||
"go.faq.a4.p1.afterPricing": "之后 $10/月,额度充裕。",
|
||||
"go.faq.a4.p2.beforeAccount": "您可以在您的",
|
||||
"go.faq.a4.p2.accountLink": "账户",
|
||||
"go.faq.a4.p3": "中管理订阅。随时取消。",
|
||||
@@ -649,8 +650,8 @@ export const dict = {
|
||||
"workspace.lite.black.message": "您当前已订阅 OpenCode Black 或在候补名单中。如需切换到 Go,请先取消订阅。",
|
||||
"workspace.lite.other.message": "此工作区中的另一位成员已经订阅了 OpenCode Go。每个工作区只有一名成员可以订阅。",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go 每月 {{price}},并提供对流行开放编码模型的可靠访问,同时享有充裕的使用限额。",
|
||||
"workspace.lite.promo.price": "$10/月",
|
||||
"OpenCode Go 起价为 {{price}},之后 $10/月,并提供对流行开放编码模型的可靠访问,同时享有充裕的使用限额。",
|
||||
"workspace.lite.promo.price": "首月 $5",
|
||||
"workspace.lite.promo.modelsTitle": "包含模型",
|
||||
"workspace.lite.promo.footer":
|
||||
"该计划主要面向国际用户,提供稳定的全球访问体验。随着我们持续了解早期使用情况并收集反馈,定价和使用限额可能会有所调整。",
|
||||
|
||||
@@ -245,7 +245,7 @@ export const dict = {
|
||||
|
||||
"go.title": "OpenCode Go | 低成本全民編碼模型",
|
||||
"go.banner.text": "DeepSeek V4 Flash 限時享有 2 倍使用額度",
|
||||
"go.meta.description": "Go 每月 $10,提供充裕的使用限額,並可穩定存取領先的編碼模型。",
|
||||
"go.meta.description": "Go 首月 $5,之後 $10/月,提供充裕的使用限額,並可穩定存取領先的編碼模型。",
|
||||
"go.hero.title": "低成本全民編碼模型",
|
||||
"go.hero.body":
|
||||
"Go 將代理編碼帶給全世界的程式設計師。提供寬裕的限額以及對最強大開源模型的穩定存取,讓你可以使用強大的代理進行構建,而無需擔心成本或可用性。",
|
||||
@@ -254,7 +254,8 @@ export const dict = {
|
||||
"go.cta.template": "{{text}} {{price}}",
|
||||
"go.cta.text": "訂閱 Go",
|
||||
"go.cta.price": "$10/月",
|
||||
"go.pricing.body": "可搭配任何代理使用。每月 $10。如有需要可儲值。隨時取消。",
|
||||
"go.cta.promo": "首月 $5",
|
||||
"go.pricing.body": "可搭配任何代理使用。首月 $5,之後 $10/月。如有需要可儲值。隨時取消。",
|
||||
"go.graph.free": "免費",
|
||||
"go.graph.freePill": "Big Pickle 與免費模型",
|
||||
"go.graph.go": "Go",
|
||||
@@ -285,20 +286,20 @@ export const dict = {
|
||||
"go.testimonials.frank.quote": "我希望我還在 Nvidia。",
|
||||
"go.problem.title": "Go 正在解決什麼問題?",
|
||||
"go.problem.body":
|
||||
"我們致力於將 OpenCode 體驗帶給盡可能多的人。OpenCode Go 是一款每月 $10 的低成本訂閱服務。它提供充裕的額度,並讓您能可靠地使用最強大的開源模型。",
|
||||
"我們致力於將 OpenCode 體驗帶給盡可能多的人。OpenCode Go 是一款低成本訂閱服務:首月 $5,之後 $10/月。它提供充裕的額度,並讓您能可靠地使用最強大的開源模型。",
|
||||
"go.problem.subtitle": " ",
|
||||
"go.problem.item1": "低成本訂閱定價",
|
||||
"go.problem.item2": "寬裕的限額與穩定存取",
|
||||
"go.problem.item3": "專為盡可能多的程式設計師打造",
|
||||
"go.problem.item4": "針對代理編碼測試的精選模型陣容",
|
||||
"go.how.title": "Go 如何運作",
|
||||
"go.how.body": "Go 每月 $10。您可以將其與 OpenCode 或任何代理搭配使用。",
|
||||
"go.how.body": "Go 起價為首月 $5,之後 $10/月。您可以將其與 OpenCode 或任何代理搭配使用。",
|
||||
"go.how.step1.title": "建立帳號",
|
||||
"go.how.step1.beforeLink": "遵循",
|
||||
"go.how.step1.link": "設定說明",
|
||||
"go.how.step2.title": "訂閱 Go",
|
||||
"go.how.step2.link": "每月 $10",
|
||||
"go.how.step2.afterLink": "額度充裕",
|
||||
"go.how.step2.link": "首月 $5",
|
||||
"go.how.step2.afterLink": "之後 $10/月,額度充裕",
|
||||
"go.how.step3.title": "開始編碼",
|
||||
"go.how.step3.body": "穩定存取開源模型",
|
||||
"go.privacy.title": "你的隱私對我們很重要",
|
||||
@@ -311,11 +312,11 @@ export const dict = {
|
||||
"go.faq.q2": "Go 包含哪些模型?",
|
||||
"go.faq.a2": "Go 包含下方列出的模型,提供充足的額度與穩定的存取。",
|
||||
"go.faq.q3": "Go 與 Zen 一樣嗎?",
|
||||
"go.faq.a3": "不。Zen 是按量付費,而 Go 每月 $10,提供充裕的限額,並可穩定存取精選模型陣容。",
|
||||
"go.faq.a3": "不。Zen 是按量付費,而 Go 首月 $5,之後 $10/月,提供充裕的限額,並可穩定存取精選模型陣容。",
|
||||
"go.faq.q4": "Go 費用是多少?",
|
||||
"go.faq.a4.p1.beforePricing": "Go 費用為",
|
||||
"go.faq.a4.p1.pricingLink": "每月 $10",
|
||||
"go.faq.a4.p1.afterPricing": "額度充裕。",
|
||||
"go.faq.a4.p1.pricingLink": "首月 $5",
|
||||
"go.faq.a4.p1.afterPricing": "之後 $10/月,額度充裕。",
|
||||
"go.faq.a4.p2.beforeAccount": "你可以在你的",
|
||||
"go.faq.a4.p2.accountLink": "帳戶",
|
||||
"go.faq.a4.p3": "中管理訂閱。隨時取消。",
|
||||
@@ -649,8 +650,8 @@ export const dict = {
|
||||
"workspace.lite.black.message": "您目前已訂閱 OpenCode Black 或在候補名單中。若要切換至 Go,請先取消訂閱。",
|
||||
"workspace.lite.other.message": "此工作區中的另一位成員已訂閱 OpenCode Go。每個工作區只能有一位成員訂閱。",
|
||||
"workspace.lite.promo.description":
|
||||
"OpenCode Go 每月 {{price}},並提供對熱門開放編碼模型的可靠存取,同時享有充裕的使用額度。",
|
||||
"workspace.lite.promo.price": "$10/月",
|
||||
"OpenCode Go 起價為 {{price}},之後 $10/月,並提供對熱門開放編碼模型的可靠存取,同時享有充裕的使用額度。",
|
||||
"workspace.lite.promo.price": "首月 $5",
|
||||
"workspace.lite.promo.modelsTitle": "包含模型",
|
||||
"workspace.lite.promo.footer":
|
||||
"此方案主要為國際使用者設計,提供穩定的全球存取服務。隨著我們從初期使用情況和回饋中持續了解需求,價格和使用額度可能會有所調整。",
|
||||
|
||||
@@ -364,7 +364,12 @@ export default function Home() {
|
||||
{(part) => {
|
||||
if (part === "{{text}}") return <span>{i18n.t("go.cta.text")}</span>
|
||||
if (part === "{{price}}") {
|
||||
return <span data-slot="cta-price">{i18n.t("go.cta.price")}</span>
|
||||
return (
|
||||
<span data-slot="cta-price">
|
||||
<span data-slot="cta-price-old">{i18n.t("go.cta.price")}</span>
|
||||
<span data-slot="cta-price-new">{i18n.t("go.cta.promo")}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return part
|
||||
}}
|
||||
|
||||
@@ -328,6 +328,7 @@ export namespace Billing {
|
||||
return LiteData.threeMonths100Coupon
|
||||
if (coupons.some((coupon) => coupon.type === "GOFREEMONTH" && !coupon.timeRedeemed))
|
||||
return LiteData.firstMonth100Coupon
|
||||
if (!coupons.some((coupon) => coupon.type === "GO1MONTH50")) return LiteData.firstMonth50Coupon
|
||||
return undefined
|
||||
})()
|
||||
const createSession = () =>
|
||||
|
||||
+10
-68
@@ -30,12 +30,10 @@ import {
|
||||
type ToolDefinition,
|
||||
type UsageInput,
|
||||
} from "@opencode/ai"
|
||||
import { Auth, Endpoint, RequestExecutor, type AnyRoute, type HttpMiddleware } from "@opencode/ai/route"
|
||||
import { Auth, Endpoint, RequestExecutor, type AnyRoute } from "@opencode/ai/route"
|
||||
import { ProviderShared } from "@opencode/ai/protocols/shared"
|
||||
import { Cause, Context, Effect, Layer, Option, Schema, Scope, Stream } from "effect"
|
||||
import { makeParser } from "effect/unstable/encoding/Sse"
|
||||
import { HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { AsyncLocalStorage } from "node:async_hooks"
|
||||
import type { ID, RuntimeInfo } from "./model.js"
|
||||
import { Provider } from "./provider.js"
|
||||
import { State } from "./state.js"
|
||||
@@ -153,11 +151,10 @@ function prepareOptions(model: RuntimeInfo, pkg: string) {
|
||||
}
|
||||
}
|
||||
|
||||
const send: Fetch = typeof customFetch === "function" ? customFetch : fetch
|
||||
const middleware = httpMiddleware.getStore()
|
||||
const res = middleware
|
||||
? await throughMiddleware(middleware, send, input, { ...opts, timeout: false })
|
||||
: await send(input, { ...opts, timeout: false })
|
||||
const res = await (typeof customFetch === "function" ? customFetch : fetch)(input, {
|
||||
...opts,
|
||||
timeout: false,
|
||||
})
|
||||
if (!chunkAbortCtl || typeof chunkTimeout !== "number") return res
|
||||
return wrapSSE(res, chunkTimeout, chunkAbortCtl)
|
||||
}
|
||||
@@ -165,56 +162,6 @@ function prepareOptions(model: RuntimeInfo, pkg: string) {
|
||||
return options
|
||||
}
|
||||
|
||||
type Fetch = (input: Parameters<typeof fetch>[0], init?: BunFetchRequestInit) => Promise<Response>
|
||||
|
||||
// HTTP hook middleware is scoped to one model request, but the SDK's fetch is baked into the
|
||||
// cached language model, so the active middleware rides along in async context instead.
|
||||
const httpMiddleware = new AsyncLocalStorage<{ http: HttpMiddleware; context: Context.Context<never> }>()
|
||||
|
||||
function throughMiddleware(
|
||||
store: { http: HttpMiddleware; context: Context.Context<never> },
|
||||
send: Fetch,
|
||||
input: Parameters<typeof fetch>[0],
|
||||
init: BunFetchRequestInit,
|
||||
) {
|
||||
const toError = (cause: unknown) => (cause instanceof Error ? cause : new Error(String(cause)))
|
||||
const request = input instanceof Request ? new Request(input, init) : new Request(String(input), init)
|
||||
return Effect.runPromiseWith(store.context)(
|
||||
Effect.gen(function* () {
|
||||
// Hooks see a byte body like on the native route, so they may convert it to a web Request
|
||||
// as many times as they like without contending for one stream.
|
||||
const body = request.body ? new Uint8Array(yield* Effect.promise(() => request.arrayBuffer())) : undefined
|
||||
const response = yield* store.http(
|
||||
body
|
||||
? HttpClientRequest.bodyUint8Array(
|
||||
HttpClientRequest.fromWeb(request),
|
||||
body,
|
||||
request.headers.get("content-type") ?? undefined,
|
||||
)
|
||||
: HttpClientRequest.fromWeb(request),
|
||||
(sent) =>
|
||||
Effect.gen(function* () {
|
||||
const web = yield* HttpClientRequest.toWeb(sent)
|
||||
const response = yield* Effect.tryPromise(async () =>
|
||||
send(web.url, {
|
||||
...init,
|
||||
method: web.method,
|
||||
headers: web.headers,
|
||||
body: web.body ? await web.arrayBuffer() : undefined,
|
||||
}),
|
||||
)
|
||||
return HttpClientResponse.fromWeb(sent, response)
|
||||
}).pipe(Effect.mapError(toError)),
|
||||
)
|
||||
const stream = [204, 205, 304].includes(response.status)
|
||||
? null
|
||||
: yield* Stream.toReadableStreamEffect(response.stream)
|
||||
return new Response(stream, { status: response.status, headers: response.headers })
|
||||
}),
|
||||
{ signal: init.signal ?? undefined },
|
||||
)
|
||||
}
|
||||
|
||||
export class InitError extends Schema.TaggedError<InitError>()("AISDK.InitError", {
|
||||
providerID: Provider.ID,
|
||||
cause: Schema.Defect(),
|
||||
@@ -397,8 +344,7 @@ function modelFromLanguage(info: RuntimeInfo, language: LanguageModelV3) {
|
||||
model: (input) =>
|
||||
LanguageModel.make({ ...input, provider: "provider" in input ? input.provider : providerID, route }),
|
||||
prepareTransport: (body) => Effect.succeed(body),
|
||||
streamPrepared: (prepared, _request, _runtime, options) =>
|
||||
streamLanguage(language, prepared as LanguageModelV3CallOptions, options?.http),
|
||||
streamPrepared: (prepared) => streamLanguage(language, prepared as LanguageModelV3CallOptions),
|
||||
}
|
||||
return LanguageModel.make({
|
||||
id: info.modelID ?? info.id,
|
||||
@@ -696,18 +642,14 @@ function metadataProviderOptions(input: ProviderMetadata | undefined): SharedV3P
|
||||
return Object.fromEntries(Object.entries(input).map(([key, value]) => [key, jsonObject(value)]))
|
||||
}
|
||||
|
||||
function streamLanguage(language: LanguageModelV3, options: LanguageModelV3CallOptions, http?: HttpMiddleware) {
|
||||
function streamLanguage(language: LanguageModelV3, options: LanguageModelV3CallOptions) {
|
||||
const state = { step: 0, toolNames: {} as Record<string, string> }
|
||||
return Stream.concat(
|
||||
Stream.make(LLMEvent.stepStart({ index: state.step })),
|
||||
Stream.unwrap(
|
||||
Effect.gen(function* () {
|
||||
const context = yield* Effect.context<never>()
|
||||
return yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
http ? httpMiddleware.run({ http, context }, () => language.doStream(options)) : language.doStream(options),
|
||||
catch: (error) => llmError(error, "request"),
|
||||
})
|
||||
Effect.tryPromise({
|
||||
try: () => language.doStream(options),
|
||||
catch: (error) => llmError(error, "request"),
|
||||
}).pipe(
|
||||
Effect.map((result) =>
|
||||
Stream.fromReadableStream({
|
||||
|
||||
@@ -379,13 +379,13 @@ export function transformSession(input: TransformInput): TransformResult {
|
||||
return []
|
||||
const content = owned.flatMap((part): Array<Record<string, unknown>> => {
|
||||
if (part.type === "text")
|
||||
return [{ type: "text", text: part.text, ...(part.metadata ? { native: part.metadata } : {}) }]
|
||||
return [{ type: "text", text: part.text, ...(part.metadata ? { state: part.metadata } : {}) }]
|
||||
if (part.type === "reasoning")
|
||||
return [
|
||||
{
|
||||
type: "reasoning",
|
||||
text: part.text,
|
||||
...(part.metadata ? { native: part.metadata } : {}),
|
||||
...(part.metadata ? { state: part.metadata } : {}),
|
||||
time: { created: part.time.start, ...(part.time.end === undefined ? {} : { completed: part.time.end }) },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -258,6 +258,7 @@ export const GithubCopilotPlugin = define({
|
||||
evt.sdk = mod.createOpenaiCompatible(evt.options)
|
||||
}),
|
||||
)
|
||||
// Runs for every route, unlike http.request, which the AI SDK route bypasses.
|
||||
yield* ctx.session.hook(
|
||||
"model.request",
|
||||
(evt) =>
|
||||
|
||||
@@ -60,7 +60,7 @@ export const latestCompaction = Effect.fnUntraced(function* (
|
||||
})
|
||||
|
||||
export const decodeMessageRow = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
decode(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type })).pipe(
|
||||
decode({ ...row.data, id: row.id, type: row.type }).pipe(
|
||||
Effect.tap((message) =>
|
||||
SessionProviderContext.isCheckpoint(message)
|
||||
? SessionProviderContext.validate(message.providerContext)
|
||||
|
||||
@@ -125,7 +125,7 @@ const promotedFromMessage = Effect.fn("SessionInbox.promotedFromMessage")(functi
|
||||
if (row === undefined) return undefined
|
||||
if (row.session_id !== sessionID || (row.type !== "user" && row.type !== "synthetic"))
|
||||
return yield* new LifecycleConflict({ id })
|
||||
const message = decodeMessage(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type }))
|
||||
const message = decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
const base = { id, sessionID, time: { created: message.time.created }, delivery }
|
||||
if (message.type === "user")
|
||||
return User.make({
|
||||
|
||||
@@ -83,9 +83,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
"session.message.content.updated": (event) =>
|
||||
updateOwnedAssistant(event.data.messageID, (draft) => {
|
||||
draft.content = castDraft(
|
||||
Schema.decodeUnknownSync(Schema.Array(SessionMessage.AssistantContent))(
|
||||
SessionMessage.persistedContent(event.data.content),
|
||||
),
|
||||
Schema.decodeUnknownSync(Schema.Array(SessionMessage.AssistantContent))(event.data.content),
|
||||
)
|
||||
}),
|
||||
"session.usage.recorded": () => Effect.void,
|
||||
@@ -224,7 +222,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
draft.error = undefined
|
||||
draft.finish = undefined
|
||||
draft.rawFinish = undefined
|
||||
draft.native = undefined
|
||||
draft.providerState = undefined
|
||||
draft.time.created = DateTime.makeUnsafe(event.data.started)
|
||||
draft.time.streamed = undefined
|
||||
draft.time.completed = undefined
|
||||
@@ -265,7 +263,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
draft.time.completed = created
|
||||
draft.finish = event.data.finish
|
||||
draft.rawFinish = event.data.rawFinish
|
||||
draft.native = castDraft(event.data.providerState)
|
||||
draft.providerState = castDraft(event.data.providerState)
|
||||
draft.cost = event.data.cost
|
||||
draft.tokens = event.data.tokens
|
||||
projectTerminalSnapshot(draft, event)
|
||||
@@ -276,7 +274,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
draft.time.completed = created
|
||||
draft.finish = event.data.finish ?? "error"
|
||||
draft.rawFinish = event.data.rawFinish
|
||||
draft.native = castDraft(event.data.providerState)
|
||||
draft.providerState = castDraft(event.data.providerState)
|
||||
draft.error = castDraft(event.data.error)
|
||||
draft.retry = undefined
|
||||
if (event.data.cost !== undefined && event.data.tokens !== undefined) {
|
||||
@@ -296,7 +294,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
const match = latestText(draft)
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.native = castDraft(event.data.state)
|
||||
match.state = castDraft(event.data.state)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -384,7 +382,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
native: event.data.state,
|
||||
state: event.data.state,
|
||||
time: { created },
|
||||
}),
|
||||
),
|
||||
@@ -397,7 +395,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.time = { created: match.time?.created ?? created, completed: created }
|
||||
if (event.data.state !== undefined) match.native = event.data.state
|
||||
if (event.data.state !== undefined) match.state = event.data.state
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -433,7 +431,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
metadata: event.metadata ? { ...current.metadata, ...event.metadata } : current.metadata,
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
native: event.data.providerState,
|
||||
providerState: event.data.providerState,
|
||||
summary: event.data.text,
|
||||
providerContext: event.data.providerContext,
|
||||
recent: event.data.recent,
|
||||
@@ -450,7 +448,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
metadata: event.metadata,
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
native: event.data.providerState,
|
||||
providerState: event.data.providerState,
|
||||
summary: event.data.text,
|
||||
providerContext: event.data.providerContext,
|
||||
recent: event.data.recent,
|
||||
|
||||
@@ -229,7 +229,7 @@ export const layer = Layer.effect(
|
||||
const entries = Object.entries(shaped.options)
|
||||
const generation = Object.fromEntries(entries.filter(([k]) => GENERATION_KEYS.has(k))) as GenerationOptionsFields
|
||||
const providerOptions = Object.fromEntries(entries.filter(([k]) => !GENERATION_KEYS.has(k)))
|
||||
const affinity = session.parentID ?? session.fork?.sessionID ?? session.id
|
||||
const root = session.fork?.sessionID ?? session.id
|
||||
const base = LLM.request({
|
||||
model: model.model,
|
||||
http: {
|
||||
@@ -244,7 +244,7 @@ export const layer = Layer.effect(
|
||||
},
|
||||
},
|
||||
// TODO: Persist cache lineage so nested forks reuse the root session's cache key.
|
||||
promptCacheKey: /^ses_[0-9a-f]{64}$/.test(affinity) ? affinity.slice(4) : affinity,
|
||||
promptCacheKey: /^ses_[0-9a-f]{64}$/.test(root) ? root.slice(4) : root,
|
||||
system: shaped.system,
|
||||
messages: boundImages(unsupportedParts(shaped.messages, model.capabilities)),
|
||||
tools: Array.from(hooked, ([name, t]) => ({ ...t, name })),
|
||||
|
||||
@@ -228,7 +228,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
||||
function run(db: DatabaseService, event: MessageEvent) {
|
||||
return Effect.gen(function* () {
|
||||
const decodeRow = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
decodeMessage(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type }))
|
||||
decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
const updateMessage = (message: SessionMessage.Info) => {
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
|
||||
@@ -107,9 +107,7 @@ const plan = Effect.fn("SessionRevert.plan")(function* (db: Database.Interface["
|
||||
const decode = Schema.decodeUnknownEffect(SessionMessage.Info)
|
||||
const files = new Map<RelativePath, Snapshot.ID>()
|
||||
for (const row of rows) {
|
||||
const message = yield* decode(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type })).pipe(
|
||||
Effect.orDie,
|
||||
)
|
||||
const message = yield* decode({ ...row.data, id: row.id, type: row.type }).pipe(Effect.orDie)
|
||||
if (message.type !== "assistant" || !message.snapshot?.start) continue
|
||||
for (const file of message.snapshot.files ?? [])
|
||||
if (!files.has(file)) files.set(file, Snapshot.ID.make(message.snapshot.start))
|
||||
|
||||
@@ -162,7 +162,7 @@ const assistant = (message: SessionMessage.Assistant, model: Model.Ref, provider
|
||||
text: item.text,
|
||||
// Text can carry provider-bound state (e.g. Gemini thought signatures),
|
||||
// which is only replayable against the model that produced it.
|
||||
providerMetadata: reuseProviderMetadata ? providerMetadata(providerMetadataKey, item.native) : undefined,
|
||||
providerMetadata: reuseProviderMetadata ? providerMetadata(providerMetadataKey, item.state) : undefined,
|
||||
},
|
||||
]
|
||||
// Let the destination adapter handle readable reasoning after a model/provider switch.
|
||||
@@ -172,7 +172,7 @@ const assistant = (message: SessionMessage.Assistant, model: Model.Ref, provider
|
||||
{
|
||||
type: "reasoning",
|
||||
text: item.text,
|
||||
providerMetadata: providerMetadata(providerMetadataKey, item.native),
|
||||
providerMetadata: providerMetadata(providerMetadataKey, item.state),
|
||||
},
|
||||
]
|
||||
: item.text.length > 0
|
||||
|
||||
@@ -269,13 +269,13 @@ function sanitizeMessage(message: SessionMessage.Info): SessionMessage.Info {
|
||||
return {
|
||||
...content,
|
||||
text: redact("text", message.id, content.text),
|
||||
native: content.native ? { redacted: `text-native:${message.id}` } : undefined,
|
||||
state: content.state ? { redacted: `text-state:${message.id}` } : undefined,
|
||||
}
|
||||
if (content.type === "reasoning")
|
||||
return {
|
||||
...content,
|
||||
text: redact("reasoning", message.id, content.text),
|
||||
native: content.native ? { redacted: `reasoning-native:${message.id}` } : undefined,
|
||||
state: content.state ? { redacted: `reasoning-state:${message.id}` } : undefined,
|
||||
}
|
||||
return {
|
||||
...content,
|
||||
@@ -299,7 +299,7 @@ function sanitizeMessage(message: SessionMessage.Info): SessionMessage.Info {
|
||||
summary: redact("compaction-summary", message.id, message.summary),
|
||||
recent: redact("compaction-recent", message.id, message.recent),
|
||||
...(message.status === "completed"
|
||||
? { native: metadata("compaction-native", message.id, message.native) }
|
||||
? { providerState: metadata("compaction-provider-state", message.id, message.providerState) }
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import { LLMClient, RequestExecutor } from "@opencode/ai/route"
|
||||
import { compileRequest } from "@opencode/ai/route/client"
|
||||
import { expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(AISDK.locationLayer)
|
||||
@@ -597,100 +596,6 @@ it.effect("does not treat SSE comment heartbeats as model progress", () =>
|
||||
}),
|
||||
)
|
||||
|
||||
const chatChunk = (text: string) =>
|
||||
`data: ${JSON.stringify({
|
||||
id: "response-1",
|
||||
object: "chat.completion.chunk",
|
||||
created: 0,
|
||||
model: "api-model",
|
||||
choices: [{ index: 0, delta: { content: text }, finish_reason: "stop" }],
|
||||
})}\n\ndata: [DONE]\n\n`
|
||||
|
||||
const compatibleModel = Effect.fn(function* (customFetch: typeof fetch) {
|
||||
const aisdk = yield* AISDK.Service
|
||||
yield* aisdk.hook.sdk((event) => {
|
||||
event.sdk = createOpenAICompatible({
|
||||
...event.options,
|
||||
name: String(event.options.name),
|
||||
baseURL: String(event.options.baseURL),
|
||||
})
|
||||
})
|
||||
return yield* aisdk.model(
|
||||
model("@ai-sdk/openai-compatible", { apiKey: "test", baseURL: "https://example.test/v1", fetch: customFetch }),
|
||||
)
|
||||
})
|
||||
|
||||
it.effect("routes AI SDK requests and responses through HTTP hook middleware", () =>
|
||||
Effect.gen(function* () {
|
||||
const sent: Array<{ url: string; headers: Headers; body: string }> = []
|
||||
const resolved = yield* compatibleModel(
|
||||
Object.assign(
|
||||
async (input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
||||
sent.push({
|
||||
url: String(input),
|
||||
headers: new Headers(init?.headers),
|
||||
body: new TextDecoder().decode(init?.body as ArrayBuffer),
|
||||
})
|
||||
return new Response(chatChunk("upstream"), { headers: { "content-type": "text/event-stream" } })
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
),
|
||||
)
|
||||
const seen: string[] = []
|
||||
const response = yield* LLMClient.generate(LLM.request({ model: resolved, prompt: "Hello" }), {
|
||||
http: (request, handler) =>
|
||||
Effect.gen(function* () {
|
||||
// Read the body twice the way session hooks do, to prove it is not a single-use stream.
|
||||
const first = yield* HttpClientRequest.toWeb(request)
|
||||
const second = yield* HttpClientRequest.toWeb(request)
|
||||
seen.push(`${request.method} ${request.url}`)
|
||||
seen.push(yield* Effect.promise(() => first.text()))
|
||||
seen.push(yield* Effect.promise(() => second.text()))
|
||||
const upstream = yield* handler(HttpClientRequest.setHeader(request, "x-hook", "applied"))
|
||||
seen.push(`status ${upstream.status}`)
|
||||
return HttpClientResponse.fromWeb(
|
||||
upstream.request,
|
||||
new Response(chatChunk("rewritten"), { headers: { "content-type": "text/event-stream" } }),
|
||||
)
|
||||
}),
|
||||
}).pipe(Effect.provide(client))
|
||||
|
||||
expect(sent).toHaveLength(1)
|
||||
expect(sent[0]?.url).toBe("https://example.test/v1/chat/completions")
|
||||
expect(sent[0]?.headers.get("x-hook")).toBe("applied")
|
||||
expect(sent[0]?.headers.get("authorization")).toBe("Bearer test")
|
||||
expect(JSON.parse(sent[0]?.body ?? "")).toMatchObject({ model: "api-model" })
|
||||
expect(seen).toEqual([
|
||||
"POST https://example.test/v1/chat/completions",
|
||||
sent[0]?.body,
|
||||
sent[0]?.body,
|
||||
"status 200",
|
||||
])
|
||||
expect(response.events.filter(LLMEvent.is.textDelta).map((event) => event.text)).toEqual(["rewritten"])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("sends AI SDK requests directly when no HTTP hook middleware is attached", () =>
|
||||
Effect.gen(function* () {
|
||||
const bodies: unknown[] = []
|
||||
const resolved = yield* compatibleModel(
|
||||
Object.assign(
|
||||
async (_input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
||||
bodies.push(init?.body)
|
||||
return new Response(chatChunk("upstream"), { headers: { "content-type": "text/event-stream" } })
|
||||
},
|
||||
{ preconnect: fetch.preconnect },
|
||||
),
|
||||
)
|
||||
const response = yield* LLMClient.generate(LLM.request({ model: resolved, prompt: "Hello" })).pipe(
|
||||
Effect.provide(client),
|
||||
)
|
||||
expect(bodies).toHaveLength(1)
|
||||
expect(typeof bodies[0]).toBe("string")
|
||||
expect(response.events.filter(LLMEvent.is.textDelta).map((event) => event.text)).toEqual(["upstream"])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("emits malformed AI SDK tool input without executing it", () =>
|
||||
Effect.gen(function* () {
|
||||
const aisdk = yield* AISDK.Service
|
||||
|
||||
@@ -30,7 +30,7 @@ describe("CodeMode", () => {
|
||||
type: "tool",
|
||||
name: "echo",
|
||||
description: "Echo text",
|
||||
signature: "tools.echo({\n text: string,\n}): Promise<string>",
|
||||
signature: "tools.echo(input: {\n text: string,\n}): Promise<string>",
|
||||
pinned: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,14 +6,14 @@ const entry = (path: string, description: string, signature?: string, pinned = f
|
||||
type: "tool",
|
||||
name: path,
|
||||
description,
|
||||
signature: signature ?? `tools.${path}({\n q: string,\n}): Promise<string>`,
|
||||
signature: signature ?? `tools.${path}(input: {\n q: string,\n}): Promise<string>`,
|
||||
pinned,
|
||||
})
|
||||
|
||||
const lookup = entry(
|
||||
"orders.lookup",
|
||||
"Look up an order by ID",
|
||||
"tools.orders.lookup({\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
|
||||
"tools.orders.lookup(input: {\n id: string,\n}): Promise<{\n id: string,\n status: string,\n}>",
|
||||
)
|
||||
|
||||
const render = (tools: CodeModeCatalog.Inventory["tools"], budget?: number) =>
|
||||
@@ -133,7 +133,7 @@ describe("CodeModeInstructions.render", () => {
|
||||
expect(partial).toContain(
|
||||
"The catalog is partial. Inside `execute`, use `search(...)` to find a tool, then call it by the `path` in the result. `search` is synchronous. Call it without `await`; it does not return a Promise.",
|
||||
)
|
||||
expect(partial).toContain("- search({")
|
||||
expect(partial).toContain("- search(input: {")
|
||||
expect(partial).toContain(" /** @integer @exclusiveMinimum 0 */\n limit?: number,")
|
||||
expect(partial).toContain(" /** @integer @minimum 0 */\n offset?: number,")
|
||||
expect(partial).not.toContain("tools.orders.lookup(input:")
|
||||
@@ -145,7 +145,7 @@ describe("CodeModeInstructions.render", () => {
|
||||
const expensive = entry(
|
||||
"alpha.expensive",
|
||||
"Expensive",
|
||||
`tools.alpha.expensive({\n aVeryLongParameterName: string,\n anotherEvenLongerParameterName: number,\n yetAnotherExtremelyVerboseParameterName: string,\n}): Promise<string>`,
|
||||
`tools.alpha.expensive(input: {\n aVeryLongParameterName: string,\n anotherEvenLongerParameterName: number,\n yetAnotherExtremelyVerboseParameterName: string,\n}): Promise<string>`,
|
||||
)
|
||||
// Round 1 places alpha.cheap and beta.cheap; in round 2 alpha.expensive does not fit,
|
||||
// which marks only alpha done - it must NOT prevent other namespaces from inlining.
|
||||
@@ -166,7 +166,7 @@ describe("CodeModeInstructions.render", () => {
|
||||
const documented = entry(
|
||||
"records.lookup",
|
||||
"Look up a record",
|
||||
`tools.records.lookup({\n /** ${"A detailed identifier description. ".repeat(20).trim()} */\n id: string,\n}): Promise<string>`,
|
||||
`tools.records.lookup(input: {\n /** ${"A detailed identifier description. ".repeat(20).trim()} */\n id: string,\n}): Promise<string>`,
|
||||
)
|
||||
const instructions = render([documented], 40)
|
||||
expect(instructions).toContain("- records (1 tool, none shown)")
|
||||
@@ -184,7 +184,7 @@ describe("CodeModeInstructions.update", () => {
|
||||
const echo = entry("notes.echo", "Echo text")
|
||||
|
||||
test("renders additions, changes, and removals as a compact semantic delta", () => {
|
||||
const changed = { ...echo, signature: "tools.notes.echo({\n text: string,\n}): Promise<string>" }
|
||||
const changed = { ...echo, signature: "tools.notes.echo(input: {\n text: string,\n}): Promise<string>" }
|
||||
const added = entry("notes.list", "List notes")
|
||||
const unchanged = Array.from({ length: 5 }, (_, index) => entry(`stable.tool${index}`, `Stable ${index}`))
|
||||
const text = update([echo, lookup, ...unchanged], [changed, added, ...unchanged])
|
||||
|
||||
@@ -13,14 +13,14 @@ const echo: CodeModeCatalog.Tool = {
|
||||
type: "tool",
|
||||
name: "notes.echo",
|
||||
description: "Echo text",
|
||||
signature: "tools.notes.echo({\n text: string,\n}): Promise<string>",
|
||||
signature: "tools.notes.echo(input: {\n text: string,\n}): Promise<string>",
|
||||
}
|
||||
|
||||
const lookup: CodeModeCatalog.Tool = {
|
||||
type: "tool",
|
||||
name: "orders.lookup",
|
||||
description: "Look up an order",
|
||||
signature: "tools.orders.lookup({\n id: string,\n}): Promise<unknown>",
|
||||
signature: "tools.orders.lookup(input: {\n id: string,\n}): Promise<unknown>",
|
||||
}
|
||||
|
||||
describe("CodeModeInstructions", () => {
|
||||
|
||||
@@ -397,7 +397,7 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
|
||||
])
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.promptCacheKey).toBe(parentID)
|
||||
expect(requests[0]?.promptCacheKey).toBe(sessionID)
|
||||
expect(requests[0]?.http?.headers).toEqual({
|
||||
"x-session-affinity": sessionID,
|
||||
"X-Session-Id": sessionID,
|
||||
|
||||
@@ -1272,7 +1272,7 @@ describe("SessionTransfer", () => {
|
||||
const runningCompactionID = SessionMessage.ID.create()
|
||||
const completedCompactionID = SessionMessage.ID.create()
|
||||
const model = Model.Ref.make({ id: Model.ID.make("model"), providerID: Provider.ID.make("provider") })
|
||||
const native = { responseId: "summary-response" }
|
||||
const providerState = { responseId: "summary-response" }
|
||||
|
||||
yield* transfer.import({
|
||||
data: {
|
||||
@@ -1328,7 +1328,7 @@ describe("SessionTransfer", () => {
|
||||
status: "completed",
|
||||
reason: "manual",
|
||||
model,
|
||||
native,
|
||||
providerState,
|
||||
summary: "summary",
|
||||
recent: "recent",
|
||||
time: { created: DateTime.makeUnsafe(9) },
|
||||
@@ -1345,10 +1345,10 @@ describe("SessionTransfer", () => {
|
||||
completedCompactionID,
|
||||
])
|
||||
expect(yield* Bus.latestSequence(db, sessionID)).toBe(4)
|
||||
expect((yield* transfer.export({ sessionID })).messages.at(-1)).toMatchObject({ model, native })
|
||||
expect((yield* transfer.export({ sessionID })).messages.at(-1)).toMatchObject({ model, providerState })
|
||||
expect((yield* transfer.export({ sessionID, sanitize: true })).messages.at(-1)).toMatchObject({
|
||||
model,
|
||||
native: { redacted: `compaction-native:${completedCompactionID}` },
|
||||
providerState: { redacted: `compaction-provider-state:${completedCompactionID}` },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -426,7 +426,7 @@ it.live("compaction hooks supply the summary instead of provider compaction", ()
|
||||
status: "completed",
|
||||
summary: "## Objective\n- hooked summary",
|
||||
recent: "",
|
||||
native: { responseId: "plugin" },
|
||||
providerState: { responseId: "plugin" },
|
||||
metadata: { plugin: "custom" },
|
||||
tokens: { input: 10, output: 5 },
|
||||
})
|
||||
|
||||
@@ -690,7 +690,7 @@ describe("SessionProjector", () => {
|
||||
type: "assistant",
|
||||
finish: "stop",
|
||||
rawFinish: "stop_sequence",
|
||||
native: { response: "ended" },
|
||||
providerState: { response: "ended" },
|
||||
cost: Money.USD.make(1),
|
||||
tokens: { input: 2, output: 3, reasoning: 4, cache: { read: 5, write: 6 } },
|
||||
snapshot: { end: "snap_ended", files: ["src/ended.ts"] },
|
||||
@@ -700,7 +700,7 @@ describe("SessionProjector", () => {
|
||||
type: "assistant",
|
||||
finish: "content-filter",
|
||||
rawFinish: "blocked",
|
||||
native: { response: "failed" },
|
||||
providerState: { response: "failed" },
|
||||
error: { type: "provider.invalid-request", message: "Failed" },
|
||||
snapshot: { end: "snap_failed", files: ["src/failed.ts"] },
|
||||
time: { completed: created },
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("toLLMMessages", () => {
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
native: { signature: "sig_1" },
|
||||
state: { signature: "sig_1" },
|
||||
}),
|
||||
]),
|
||||
],
|
||||
@@ -711,7 +711,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
native: { signature: "sig_1" },
|
||||
state: { signature: "sig_1" },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
@@ -860,7 +860,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
native: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -891,7 +891,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
native: { signature: "signed" },
|
||||
state: { signature: "signed" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -918,7 +918,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Partial thought",
|
||||
native: { itemId: "rs_failed", reasoningEncryptedContent: null },
|
||||
state: { itemId: "rs_failed", reasoningEncryptedContent: null },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
@@ -1016,7 +1016,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Visible thought",
|
||||
native: { signature: "sig_old" },
|
||||
state: { signature: "sig_old" },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
@@ -1110,7 +1110,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Visible thought",
|
||||
native: { reasoningEncryptedContent: "encrypted" },
|
||||
state: { reasoningEncryptedContent: "encrypted" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -1140,7 +1140,7 @@ Recent work
|
||||
SessionMessage.AssistantText.make({
|
||||
type: "text",
|
||||
text: "Checking.",
|
||||
native: { phase: "commentary" },
|
||||
state: { phase: "commentary" },
|
||||
}),
|
||||
],
|
||||
error: { type: "provider.unknown", message: "Interrupted after commentary" },
|
||||
@@ -1171,7 +1171,7 @@ Recent work
|
||||
SessionMessage.AssistantText.make({
|
||||
type: "text",
|
||||
text: "Checking.",
|
||||
native: { phase: "commentary" },
|
||||
state: { phase: "commentary" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
|
||||
@@ -2558,7 +2558,7 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(s.executions).toEqual(["x".repeat(4_000)])
|
||||
expect((yield* s.messages).find((message) => message.type === "compaction")).toMatchObject({
|
||||
model: { id: s.currentModel.id, providerID: s.currentModel.provider, variant },
|
||||
native: { responseId: "summary" },
|
||||
providerState: { responseId: "summary" },
|
||||
})
|
||||
|
||||
// Compare wire content without the cache breakpoints that move to the new final message.
|
||||
@@ -3508,12 +3508,12 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Signed thought",
|
||||
native: { signature: "sig_1" },
|
||||
state: { signature: "sig_1" },
|
||||
},
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Encrypted thought",
|
||||
native: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
},
|
||||
]),
|
||||
])
|
||||
@@ -3565,7 +3565,7 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "thinking",
|
||||
native: { reasoningField: "reasoning", reasoningDetails: details },
|
||||
state: { reasoningField: "reasoning", reasoningDetails: details },
|
||||
},
|
||||
{ type: "text", text: "Hello world" },
|
||||
]),
|
||||
@@ -3609,7 +3609,7 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(yield* s.context).toMatchObject([
|
||||
Expected.user("Check first"),
|
||||
Expected.assistant({}, [
|
||||
{ type: "text", text: "Checking.", native: { itemId: "msg_commentary", phase: "commentary" } },
|
||||
{ type: "text", text: "Checking.", state: { itemId: "msg_commentary", phase: "commentary" } },
|
||||
]),
|
||||
])
|
||||
|
||||
@@ -4411,7 +4411,7 @@ describe("SessionRunnerLLM", () => {
|
||||
})
|
||||
})
|
||||
|
||||
scenario("uses parent cache affinity for child model requests", function* (s) {
|
||||
scenario("adds the parent session header to child model requests", function* (s) {
|
||||
const parentID = Session.ID.make("ses_runner_parent")
|
||||
|
||||
yield* s.db
|
||||
@@ -4423,7 +4423,6 @@ describe("SessionRunnerLLM", () => {
|
||||
yield* s.runPrompt("Run child request")
|
||||
|
||||
expect(s.requests[0]?.http?.headers?.["x-parent-session-id"]).toBe(parentID)
|
||||
expect(s.requests[0]?.promptCacheKey).toBe(parentID)
|
||||
})
|
||||
|
||||
scenario("runs different sessions concurrently", function* (s) {
|
||||
@@ -4966,7 +4965,7 @@ describe("SessionRunnerLLM", () => {
|
||||
type: "assistant",
|
||||
finish: "stop",
|
||||
rawFinish: "end_turn",
|
||||
native: { responseId: "response-1", serviceTier: "priority" },
|
||||
providerState: { responseId: "response-1", serviceTier: "priority" },
|
||||
content: [Expected.text("Complete")],
|
||||
},
|
||||
])
|
||||
@@ -4997,7 +4996,7 @@ describe("SessionRunnerLLM", () => {
|
||||
type: "assistant",
|
||||
finish: "content-filter",
|
||||
rawFinish: "SAFETY",
|
||||
native: {
|
||||
providerState: {
|
||||
responseId: "response-blocked",
|
||||
refusal: { category: "safety", explanation: "Prompt blocked" },
|
||||
},
|
||||
@@ -5444,7 +5443,7 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
native: { itemId: "rs_disconnected", reasoningEncryptedContent: "encrypted-state" },
|
||||
state: { itemId: "rs_disconnected", reasoningEncryptedContent: "encrypted-state" },
|
||||
},
|
||||
]),
|
||||
{ type: "synthetic", text: INCOMPLETE_STREAM_CONTINUATION },
|
||||
|
||||
@@ -337,8 +337,8 @@ describe("V1Migration.transformSession", () => {
|
||||
agent: "build",
|
||||
model: { id: "model", providerID: "provider", variant: "fast" },
|
||||
content: [
|
||||
{ type: "text", text: "", native: { separator: true } },
|
||||
{ type: "reasoning", text: "think", native: { provider: 1 }, time: { created: 21, completed: 22 } },
|
||||
{ type: "text", text: "", state: { separator: true } },
|
||||
{ type: "reasoning", text: "think", state: { provider: 1 }, time: { created: 21, completed: 22 } },
|
||||
],
|
||||
snapshot: { start: "snap_start", end: "snap_end", files: ["a.ts", "b.ts", "c.ts"] },
|
||||
finish: "stop",
|
||||
|
||||
@@ -17,12 +17,6 @@ const channels = [
|
||||
{ channel: "prod", appId: "ai.opencode.desktop" },
|
||||
] as const
|
||||
|
||||
test("signs the macOS app without signing the DMG", async () => {
|
||||
const config = (await import("./electron-builder.config.ts?mac-signing")).default as Configuration
|
||||
expect(config.mac?.sign).toBeFunction()
|
||||
expect(config.dmg?.sign).not.toBe(true)
|
||||
})
|
||||
|
||||
for (const channel of channels) {
|
||||
test(`disables security code AutoFill by default for ${channel.channel}`, async () => {
|
||||
const previous = process.env.OPENCODE_CHANNEL
|
||||
|
||||
@@ -123,6 +123,9 @@ const getBase = (appId: string): Configuration => ({
|
||||
notarize: true,
|
||||
target: ["dmg", "zip"],
|
||||
},
|
||||
dmg: {
|
||||
sign: true,
|
||||
},
|
||||
protocols: {
|
||||
name: "OpenCode",
|
||||
schemes: ["opencode"],
|
||||
|
||||
@@ -17320,7 +17320,7 @@
|
||||
"rawFinish": {
|
||||
"type": "string"
|
||||
},
|
||||
"native": {
|
||||
"providerState": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_4"
|
||||
},
|
||||
"cost": {
|
||||
@@ -17349,7 +17349,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"native": {
|
||||
"state": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_1"
|
||||
},
|
||||
"time": {
|
||||
@@ -17396,7 +17396,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"native": {
|
||||
"state": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState"
|
||||
}
|
||||
},
|
||||
@@ -17509,7 +17509,7 @@
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Model.Ref"
|
||||
},
|
||||
"native": {
|
||||
"providerState": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_5"
|
||||
},
|
||||
"summary": {
|
||||
|
||||
@@ -601,7 +601,7 @@ export namespace Compaction {
|
||||
...Base,
|
||||
reason: Started.data.fields.reason,
|
||||
model: SessionMessage.CompactionCompleted.fields.model,
|
||||
providerState: SessionMessage.CompactionCompleted.fields.native,
|
||||
providerState: SessionMessage.CompactionCompleted.fields.providerState,
|
||||
providerContext: SessionMessage.CompactionCompleted.fields.providerContext,
|
||||
text: Schema.String,
|
||||
recent: Schema.String,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export * as SessionMessage from "./session-message.js"
|
||||
|
||||
import { Predicate, Schema, Struct } from "effect"
|
||||
import { Schema } from "effect"
|
||||
import { SessionProviderContext } from "./session-provider-context.js"
|
||||
import { optional } from "./schema.js"
|
||||
import { Content } from "./tool.js"
|
||||
@@ -177,14 +177,14 @@ export interface AssistantText extends Schema.Schema.Type<typeof AssistantText>
|
||||
export const AssistantText = Schema.Struct({
|
||||
type: Schema.tag("text"),
|
||||
text: Schema.String,
|
||||
native: ProviderState.pipe(optional),
|
||||
state: ProviderState.pipe(optional),
|
||||
}).annotate({ identifier: "Session.Message.Assistant.Text" })
|
||||
|
||||
export interface AssistantReasoning extends Schema.Schema.Type<typeof AssistantReasoning> {}
|
||||
export const AssistantReasoning = Schema.Struct({
|
||||
type: Schema.tag("reasoning"),
|
||||
text: Schema.String,
|
||||
native: ProviderState.pipe(optional),
|
||||
state: ProviderState.pipe(optional),
|
||||
time: Schema.Struct({
|
||||
created: DateTimeUtcFromMillis,
|
||||
completed: DateTimeUtcFromMillis.pipe(optional),
|
||||
@@ -196,18 +196,7 @@ export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning,
|
||||
)
|
||||
export type AssistantContent = AssistantText | AssistantReasoning | AssistantTool
|
||||
|
||||
/**
|
||||
* Frozen at the shape older releases stored: text and reasoning carried their
|
||||
* provider blob as `state`. Only replayed durable events still use it; read it
|
||||
* through `persistedContent` before decoding as `AssistantContent`.
|
||||
*/
|
||||
export const AssistantContentEncoded = Schema.toEncoded(
|
||||
Schema.Union([
|
||||
Schema.Struct({ ...Struct.omit(AssistantText.fields, ["native"]), state: ProviderState.pipe(optional) }),
|
||||
Schema.Struct({ ...Struct.omit(AssistantReasoning.fields, ["native"]), state: ProviderState.pipe(optional) }),
|
||||
AssistantTool,
|
||||
]).pipe(Schema.toTaggedUnion("type")),
|
||||
).annotate({
|
||||
export const AssistantContentEncoded = Schema.toEncoded(AssistantContent).annotate({
|
||||
identifier: "Session.Message.AssistantContent.Encoded",
|
||||
})
|
||||
export type AssistantContentEncoded = typeof AssistantContentEncoded.Type
|
||||
@@ -233,7 +222,7 @@ export const Assistant = Schema.Struct({
|
||||
}).pipe(optional),
|
||||
finish: FinishReason.pipe(optional),
|
||||
rawFinish: Schema.String.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
providerState: ProviderState.pipe(optional),
|
||||
cost: Money.USD.pipe(optional),
|
||||
tokens: TokenUsage.Info.pipe(optional),
|
||||
error: SessionError.Error.pipe(optional),
|
||||
@@ -269,7 +258,7 @@ export const CompactionCompleted = Schema.Struct({
|
||||
status: Schema.tag("completed"),
|
||||
reason: Schema.Literals(["auto", "manual"]),
|
||||
model: Model.Ref.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
providerState: ProviderState.pipe(optional),
|
||||
summary: Schema.String,
|
||||
recent: Schema.String,
|
||||
providerContext: SessionProviderContext.Info.pipe(optional),
|
||||
@@ -329,29 +318,3 @@ export type Info =
|
||||
| Compaction
|
||||
| Idle
|
||||
export type Type = Info["type"]
|
||||
|
||||
/** Reads messages stored before provider blobs were renamed to `native`. Tool parts are unchanged. */
|
||||
export function persisted(input: unknown) {
|
||||
if (!Predicate.isObject(input)) return input
|
||||
const message =
|
||||
input.type === "assistant" || input.type === "compaction" ? rename(input, "providerState", "native") : input
|
||||
if (message.type !== "assistant" || !Array.isArray(message.content)) return message
|
||||
const content = persistedContent(message.content)
|
||||
return content === message.content ? message : { ...message, content }
|
||||
}
|
||||
|
||||
/** Reads assistant content stored before text and reasoning blobs were renamed to `native`. */
|
||||
export function persistedContent(content: ReadonlyArray<unknown>) {
|
||||
const next = content.map((part) => {
|
||||
if (!Predicate.isObject(part) || (part.type !== "text" && part.type !== "reasoning")) return part
|
||||
return rename(part, "state", "native")
|
||||
})
|
||||
return next.every((part, index) => part === content[index]) ? content : next
|
||||
}
|
||||
|
||||
function rename(record: Record<string, unknown>, from: string, to: string) {
|
||||
if (record[from] === undefined || record[to] !== undefined) return record
|
||||
const value = record[from]
|
||||
const rest = Object.fromEntries(Object.entries(record).filter(([key]) => key !== from))
|
||||
return { ...rest, [to]: value }
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user