pinecall 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/lib/pinecall/agent/config.rb +114 -26
- data/lib/pinecall/version.rb +1 -1
- data/lib/pinecall/wire/shapes_config.rb +7 -2
- data/lib/pinecall/wire/shapes_doors.rb +1 -0
- data/sig/pinecall.rbs +16 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e3afca7edf6bc33bb5b6fc9ebed23d76eecac2bb6039fb243d252d379e509e50
|
|
4
|
+
data.tar.gz: f3a39bb373fa2565bc6f49f04429d8f560dcc5cd8d01f9cc8d874f3ae452808d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd6ea499f8cdbe11a9faf6e25715848c470755e8318705beee191db517446096866e79bd830c32ba77ed4c38c7da134ccdacf90ae973c7169d18baa5a58abd93
|
|
7
|
+
data.tar.gz: 9ee25075169e9581cafd1c58a67eee8b47705cc82bbd3d6aa8568b1a35db6ac20039089bb72de8004bbd973772679725208c3fdca5d469a8b292b2de53badd63
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ version number is the human's call.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.0.6] — A greeting is words or :improvise, a hangup is words or true, and the ears choose who ends the turn (2026-10-10)
|
|
10
|
+
|
|
11
|
+
- `greeting "…"` (said as written), `greeting :improvise` (the model opens on its prompt) and
|
|
12
|
+
`greeting improvise("…")` (with an instruction); `interruptible: true` for an opening the caller
|
|
13
|
+
may cut short, which by default they cannot. The `say:` / `reply:` keywords are gone.
|
|
14
|
+
- `hangup "…"` (when, in your words) or `hangup true` (whenever the model judges); `when:` is gone.
|
|
15
|
+
- `stt "…", end_of_turn: :stt | :livekit | :smart_turn`: who says the caller's turn is over, as
|
|
16
|
+
@pinecall/agents 0.9.26 says it. Needs runtime 0.1.43.
|
|
17
|
+
|
|
18
|
+
## [0.0.5] — The class declares its voice, its models and the rest, and wins (2026-10-10)
|
|
19
|
+
|
|
20
|
+
- **A class declares its environment again, and it wins over the settings.** Class macros `voice
|
|
21
|
+
"<vendor>", "<id>", model:`, `llm "<vendor>/<model>", temperature:`, `stt "<vendor>/<model>"`,
|
|
22
|
+
`language`, `greeting`, `hangup`, `turn`, `says`, `hears`, `knowledge`, `docs`, `memory` and
|
|
23
|
+
`record`, sent on `agent.configure`. A field the class declares is not read from the settings,
|
|
24
|
+
the console shows it "set by the class", and `pinecall agent set` of it is refused. Needs runtime
|
|
25
|
+
0.1.42.
|
|
26
|
+
- `builds:` and `options:` on `voice`, `llm` and `stt`: a class of the vendor's plugin and its
|
|
27
|
+
keyword arguments (`llm "openai/gpt-5.4-mini", builds: "responses.LLM", options: { use_websocket:
|
|
28
|
+
true }`), only on the org's own key for the vendor. The refusal that named a CLI verb is gone.
|
|
29
|
+
|
|
9
30
|
## [0.0.4] — A tool announces itself (2026-10-09)
|
|
10
31
|
|
|
11
32
|
### Added
|
|
@@ -4,35 +4,54 @@ module Pinecall
|
|
|
4
4
|
class Agent
|
|
5
5
|
# Class-level configuration (state lives on the instance).
|
|
6
6
|
#
|
|
7
|
-
# A class declares its contract: tools, state, view.
|
|
8
|
-
# language, greeting, memory, knowledge
|
|
9
|
-
#
|
|
7
|
+
# A class declares its contract: tools, state, view. It may also declare its environment —
|
|
8
|
+
# voice, models, language, greeting, memory, knowledge — and whatever it declares wins over the
|
|
9
|
+
# agent's settings for that field.
|
|
10
10
|
module Config
|
|
11
11
|
# `phone`, `whatsapp` and `web` are accepted for compatibility but ignored. `channel_rules
|
|
12
12
|
# false` leaves the `<channel>` block out of the prompt.
|
|
13
13
|
AS_WRITTEN = %i[phone whatsapp web channel_rules].freeze
|
|
14
14
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
knowledge: "pinecall agent knowledge edit — what the agent knows by heart is a setting, not a file",
|
|
29
|
-
docs: "pinecall docs push, then pinecall docs attach <base>"
|
|
30
|
-
}.freeze
|
|
15
|
+
# The settings a class may declare. Must match the TypeScript package's `ENVIRONMENT`.
|
|
16
|
+
ENVIRONMENT = %i[language voice llm stt greeting hangup turn says hears knowledge docs memory record].freeze
|
|
17
|
+
|
|
18
|
+
# The ones written as the wire's own fields, keyword by keyword: `greeting say: "…"`.
|
|
19
|
+
AS_KEYWORDS = %i[turn knowledge docs memory].freeze
|
|
20
|
+
|
|
21
|
+
NO_VOICE = "a voice the class declares names its vendor and the voice: voice \"<vendor>\", \"<voice id>\""
|
|
22
|
+
|
|
23
|
+
NO_OPENING = "a greeting is the words, as a string, or :improvise for the model's own: improvise(\"…\") gives it an instruction"
|
|
24
|
+
NO_ENDING = "hangup is when the model may end the call, in your words, or true whenever it judges the call done"
|
|
25
|
+
|
|
26
|
+
# The model opens the call: on its prompt alone (an empty instruction), or with this one.
|
|
27
|
+
Improvised = Data.define(:instruction, :interruptible)
|
|
31
28
|
|
|
32
29
|
NOTHING = Object.new.freeze
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
# An opening as the wire says it: words to `say`, or a `reply` the model opens on.
|
|
32
|
+
def self.greeting_of(opening, interruptible)
|
|
33
|
+
said = case opening
|
|
34
|
+
when :improvise then { reply: "" }
|
|
35
|
+
when Improvised then { reply: opening.instruction }
|
|
36
|
+
when String then { say: opening }
|
|
37
|
+
else raise DeclarationRefused, NO_OPENING
|
|
38
|
+
end
|
|
39
|
+
given = opening.is_a?(Improvised) ? opening.interruptible : interruptible
|
|
40
|
+
given.nil? ? said : { **said, allow_interruptions: given }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# When the model may end the call: an empty `when` is whenever it judges.
|
|
44
|
+
def self.hangup_of(ending)
|
|
45
|
+
return { when: "" } if ending == true
|
|
46
|
+
raise DeclarationRefused, NO_ENDING unless ending.is_a?(String) && !ending.strip.empty?
|
|
47
|
+
|
|
48
|
+
{ when: ending }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# `vendor/model`, or a vendor alone; the model id keeps every slash after the vendor's.
|
|
52
|
+
def self.model_of(named)
|
|
53
|
+
provider, _slash, model = named.to_s.partition("/")
|
|
54
|
+
{ provider:, model: }
|
|
36
55
|
end
|
|
37
56
|
|
|
38
57
|
module Declaring
|
|
@@ -45,13 +64,81 @@ module Pinecall
|
|
|
45
64
|
end
|
|
46
65
|
end
|
|
47
66
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
67
|
+
# The voice, by its vendor and the vendor's own id for it. Declared, it wins over the
|
|
68
|
+
# agent's settings; `builds` and `options` reach the vendor's plugin.
|
|
69
|
+
def voice(provider = NOTHING, voice_id = nil, model: nil, builds: nil, options: nil)
|
|
70
|
+
return environment[:voice] if provider.equal?(NOTHING)
|
|
71
|
+
raise DeclarationRefused, NO_VOICE if voice_id.nil?
|
|
72
|
+
|
|
73
|
+
environment[:voice] = { provider:, voice_id:, model:, builds:, options: }.compact
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The model that answers, `vendor/model` or a vendor alone:
|
|
77
|
+
# `llm "openai/gpt-5.4-mini", builds: "responses.LLM", options: { use_websocket: true }`.
|
|
78
|
+
def llm(model = NOTHING, temperature: nil, builds: nil, options: nil)
|
|
79
|
+
return environment[:llm] if model.equal?(NOTHING)
|
|
80
|
+
|
|
81
|
+
environment[:llm] = { **Config.model_of(model), temperature:, builds:, options: }.compact
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# The ears, `vendor/model` or a vendor alone, and who says the caller's turn is over:
|
|
85
|
+
# `end_of_turn: "stt"` the ears themselves (Deepgram Flux), `"livekit"` or `"smart-turn"`.
|
|
86
|
+
def stt(model = NOTHING, builds: nil, options: nil, end_of_turn: nil)
|
|
87
|
+
return environment[:stt] if model.equal?(NOTHING)
|
|
88
|
+
|
|
89
|
+
ends = end_of_turn&.to_s&.tr("_", "-")
|
|
90
|
+
environment[:stt] = { **Config.model_of(model), builds:, options:, end_of_turn: ends }.compact
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# How a call opens: the words, said as written, or :improvise for the model's own. The
|
|
94
|
+
# caller cannot cut it short unless `interruptible: true`.
|
|
95
|
+
def greeting(opening = NOTHING, interruptible: nil)
|
|
96
|
+
return environment[:greeting] if opening.equal?(NOTHING)
|
|
97
|
+
|
|
98
|
+
environment[:greeting] = Config.greeting_of(opening, interruptible)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The model opens the call with this instruction: `greeting improvise("…")`.
|
|
102
|
+
def improvise(instruction = "", interruptible: nil) = Improvised.new(instruction, interruptible)
|
|
103
|
+
|
|
104
|
+
# When the model may end the call: in your words, or `true` whenever it judges it done.
|
|
105
|
+
def hangup(ending = NOTHING)
|
|
106
|
+
return environment[:hangup] if ending.equal?(NOTHING)
|
|
107
|
+
|
|
108
|
+
environment[:hangup] = Config.hangup_of(ending)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# `language "es"`, `says [{ word: "GSA", spoken: "ge ese a" }]`, `record false`.
|
|
112
|
+
%i[language says record].each do |field|
|
|
113
|
+
define_method(field) do |value = NOTHING|
|
|
114
|
+
return environment[field] if value.equal?(NOTHING)
|
|
115
|
+
|
|
116
|
+
environment[field] = value
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The words the ears must know: `hears "Vidal", "Sanitas"`.
|
|
121
|
+
def hears(*words)
|
|
122
|
+
return environment[:hears] if words.empty?
|
|
123
|
+
|
|
124
|
+
environment[:hears] = words.flatten
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# `turn endpointing_ms: 300`, `docs base: "…", k: 4`, `memory remember: […], forget: […]`,
|
|
128
|
+
# `knowledge path: "…", text: "…"`.
|
|
129
|
+
AS_KEYWORDS.each do |field|
|
|
130
|
+
define_method(field) do |**given|
|
|
131
|
+
return environment[field] if given.empty?
|
|
132
|
+
|
|
133
|
+
environment[field] = given
|
|
52
134
|
end
|
|
53
135
|
end
|
|
54
136
|
|
|
137
|
+
# What the class declares of its environment, including inherited values.
|
|
138
|
+
def environment
|
|
139
|
+
@environment ||= superclass.respond_to?(:environment) ? superclass.environment.dup : {}
|
|
140
|
+
end
|
|
141
|
+
|
|
55
142
|
# Set the class docstring explicitly, for classes with no source file.
|
|
56
143
|
def doc(text = NOTHING)
|
|
57
144
|
return Doc.for_class(self) if text.equal?(NOTHING)
|
|
@@ -84,9 +171,10 @@ module Pinecall
|
|
|
84
171
|
@slug = name
|
|
85
172
|
end
|
|
86
173
|
|
|
87
|
-
# The AgentConfig sent to the gateway.
|
|
174
|
+
# The AgentConfig sent to the gateway: the contract, and the environment the class declares.
|
|
88
175
|
def wire_config(tools: nil)
|
|
89
176
|
{
|
|
177
|
+
**environment,
|
|
90
178
|
prompt: Prompt::FRAMEWORK,
|
|
91
179
|
tools:,
|
|
92
180
|
state_fields: state_field_specs,
|
data/lib/pinecall/version.rb
CHANGED
|
@@ -14,12 +14,17 @@ module Pinecall
|
|
|
14
14
|
name: { kind: :str },
|
|
15
15
|
provider: { kind: :str },
|
|
16
16
|
model: { kind: :str },
|
|
17
|
-
voice_id: { kind: :str }
|
|
17
|
+
voice_id: { kind: :str },
|
|
18
|
+
builds: { kind: :str },
|
|
19
|
+
options: { kind: :json }
|
|
18
20
|
}.freeze,
|
|
19
21
|
"ModelConfig" => {
|
|
20
22
|
provider: { kind: :str, required: true },
|
|
21
23
|
model: { kind: :str, required: true },
|
|
22
|
-
temperature: { kind: :float }
|
|
24
|
+
temperature: { kind: :float },
|
|
25
|
+
builds: { kind: :str },
|
|
26
|
+
options: { kind: :json },
|
|
27
|
+
end_of_turn: { kind: :enum, values: %w[stt livekit smart-turn] }
|
|
23
28
|
}.freeze,
|
|
24
29
|
"TurnConfig" => {
|
|
25
30
|
min_interruption_words: { kind: :int },
|
|
@@ -18,6 +18,7 @@ module Pinecall
|
|
|
18
18
|
}.freeze,
|
|
19
19
|
"AgentConfig" => {
|
|
20
20
|
prompt: { kind: :list, items: { kind: :ref, ref: "PromptBlockSpec" } },
|
|
21
|
+
language: { kind: :str },
|
|
21
22
|
greeting: { kind: :ref, ref: "GreetingConfig" },
|
|
22
23
|
voice: { kind: :ref, ref: "VoiceConfig" },
|
|
23
24
|
llm: { kind: :ref, ref: "ModelConfig" },
|
data/sig/pinecall.rbs
CHANGED
|
@@ -84,18 +84,22 @@ module Pinecall
|
|
|
84
84
|
def self.panel: (?String) { (Panel::Who) -> void } -> Panel::Declared
|
|
85
85
|
def self.declared_panel: () -> Panel::Declared?
|
|
86
86
|
|
|
87
|
-
# the
|
|
88
|
-
def self.voice: (
|
|
89
|
-
def self.llm: (
|
|
90
|
-
def self.stt: (
|
|
91
|
-
def self.
|
|
92
|
-
def self.
|
|
93
|
-
def self.
|
|
94
|
-
def self.hears: (*
|
|
95
|
-
def self.
|
|
96
|
-
def self.
|
|
97
|
-
def self.
|
|
98
|
-
def self.
|
|
87
|
+
# the environment the class declares, each over the agent's settings
|
|
88
|
+
def self.voice: (?String, ?String?, ?model: String?, ?builds: String?, ?options: Hash[untyped, untyped]?) -> untyped
|
|
89
|
+
def self.llm: (?String, ?temperature: Float?, ?builds: String?, ?options: Hash[untyped, untyped]?) -> untyped
|
|
90
|
+
def self.stt: (?String, ?builds: String?, ?options: Hash[untyped, untyped]?, ?end_of_turn: (String | Symbol)?) -> untyped
|
|
91
|
+
def self.language: (?String) -> untyped
|
|
92
|
+
def self.says: (?Array[Hash[Symbol, String]]) -> untyped
|
|
93
|
+
def self.record: (?bool) -> untyped
|
|
94
|
+
def self.hears: (*String | Array[String]) -> untyped
|
|
95
|
+
def self.greeting: (?(String | Symbol | untyped), ?interruptible: bool?) -> untyped
|
|
96
|
+
def self.improvise: (?String, ?interruptible: bool?) -> untyped
|
|
97
|
+
def self.hangup: (?(String | true)) -> untyped
|
|
98
|
+
def self.turn: (**untyped) -> untyped
|
|
99
|
+
def self.knowledge: (**untyped) -> untyped
|
|
100
|
+
def self.docs: (**untyped) -> untyped
|
|
101
|
+
def self.memory: (**untyped) -> untyped
|
|
102
|
+
def self.environment: () -> Hash[Symbol, untyped]
|
|
99
103
|
|
|
100
104
|
def self.doc: (?String) -> String?
|
|
101
105
|
def self.slug: (?String) -> String
|